diff --git a/.github/workflows/cpp-lint.yml b/.github/workflows/cpp-lint.yml new file mode 100644 index 0000000..1b6d5b8 --- /dev/null +++ b/.github/workflows/cpp-lint.yml @@ -0,0 +1,28 @@ +name: Lint C++ Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/cpp/**, .github/workflows/cpp-lint.yml] + pull_request: + paths: [frameworks/web-server/cpp/**, .github/workflows/cpp-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: C++ format + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-cpp + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install clang-format + run: sudo apt-get update -qq && sudo apt-get install -y -qq clang-format + - name: Check formatting + run: find frameworks/web-server/cpp -name '*.cpp' -print0 | xargs -0 clang-format --dry-run --Werror diff --git a/.github/workflows/csharp-lint.yml b/.github/workflows/csharp-lint.yml new file mode 100644 index 0000000..1fc4fe3 --- /dev/null +++ b/.github/workflows/csharp-lint.yml @@ -0,0 +1,32 @@ +name: Lint C# Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/csharp/**, .github/workflows/csharp-lint.yml] + pull_request: + paths: [frameworks/web-server/csharp/**, .github/workflows/csharp-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: C# format + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-csharp + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: 9.0.x + - name: Check formatting + shell: bash + run: | + find frameworks/web-server/csharp -name '*.csproj' -print0 | + xargs -0 -n1 dotnet format --verify-no-changes diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml new file mode 100644 index 0000000..5f4c982 --- /dev/null +++ b/.github/workflows/go-lint.yml @@ -0,0 +1,38 @@ +name: Lint Go Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/go/**, .github/workflows/go-lint.yml] + pull_request: + paths: [frameworks/web-server/go/**, .github/workflows/go-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Go format and vet + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-go + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-go@v7 + with: + go-version: 1.23.0 + check-latest: false + cache-dependency-path: frameworks/web-server/go/*/go.mod + - name: Check formatting + run: test -z "$(find frameworks/web-server/go -name '*.go' -print0 | xargs -0 gofmt -l)" + - name: Run vet + shell: bash + run: | + find frameworks/web-server/go -name go.mod -print0 | while IFS= read -r -d '' manifest; do + module=$(dirname "$manifest") + (cd "$module" && go vet ./...) + done diff --git a/.github/workflows/java-lint.yml b/.github/workflows/java-lint.yml new file mode 100644 index 0000000..70a4ef4 --- /dev/null +++ b/.github/workflows/java-lint.yml @@ -0,0 +1,37 @@ +name: Lint Java Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/java/**, .github/workflows/java-lint.yml] + pull_request: + paths: [frameworks/web-server/java/**, .github/workflows/java-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Java compile + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-java + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: 21 + cache: maven + cache-dependency-path: frameworks/web-server/java/**/pom.xml + - name: Compile framework sources + shell: bash + run: | + find frameworks/web-server/java -name pom.xml -print0 | while IFS= read -r -d '' manifest; do + module=$(dirname "$manifest") + (cd "$module" && mvn -B -q -DskipTests package) + done diff --git a/.github/workflows/javascript-lint.yml b/.github/workflows/javascript-lint.yml new file mode 100644 index 0000000..97b7efe --- /dev/null +++ b/.github/workflows/javascript-lint.yml @@ -0,0 +1,29 @@ +name: Lint JavaScript Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/javascript/**, .github/workflows/javascript-lint.yml] + pull_request: + paths: [frameworks/web-server/javascript/**, .github/workflows/javascript-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: JavaScript format + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-javascript + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + - name: Check formatting + run: bunx --bun prettier@3.5.3 --check 'frameworks/web-server/javascript/**/*.{js,ts}' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc928cd..9897a50 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,24 +1,24 @@ -name: Lint +name: Lint Rust Workspace on: push: branches: - main paths: - - "**.rs" - - "Cargo.toml" - - "Cargo.lock" - - "crates/**" - - ".github/workflows/lint.yml" + - Cargo.toml + - Cargo.lock + - crates/** + - scripts/check_conventions.sh + - .github/workflows/lint.yml pull_request: branches: - main paths: - - "**.rs" - - "Cargo.toml" - - "Cargo.lock" - - "crates/**" - - ".github/workflows/lint.yml" + - Cargo.toml + - Cargo.lock + - crates/** + - scripts/check_conventions.sh + - .github/workflows/lint.yml workflow_dispatch: jobs: diff --git a/.github/workflows/php-lint.yml b/.github/workflows/php-lint.yml new file mode 100644 index 0000000..52b35d3 --- /dev/null +++ b/.github/workflows/php-lint.yml @@ -0,0 +1,26 @@ +name: Lint PHP Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/php/**, .github/workflows/php-lint.yml] + pull_request: + paths: [frameworks/web-server/php/**, .github/workflows/php-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: PHP syntax + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-php + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check syntax + run: find frameworks/web-server/php -name '*.php' -print0 | xargs -0 -n1 php -l diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml new file mode 100644 index 0000000..147a038 --- /dev/null +++ b/.github/workflows/python-lint.yml @@ -0,0 +1,31 @@ +name: Lint Python Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/python/**, .github/workflows/python-lint.yml] + pull_request: + paths: [frameworks/web-server/python/**, .github/workflows/python-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Python ruff + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-python + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: '3.13' + - name: Install ruff + run: python -m pip install ruff==0.9.10 + - name: Check lint and format + run: ruff check frameworks/web-server/python && ruff format --check frameworks/web-server/python diff --git a/.github/workflows/ruby-lint.yml b/.github/workflows/ruby-lint.yml new file mode 100644 index 0000000..c51b045 --- /dev/null +++ b/.github/workflows/ruby-lint.yml @@ -0,0 +1,29 @@ +name: Lint Ruby Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/ruby/**, .github/workflows/ruby-lint.yml] + pull_request: + paths: [frameworks/web-server/ruby/**, .github/workflows/ruby-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Ruby syntax + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-ruby + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + - name: Check syntax + run: find frameworks/web-server/ruby -name '*.rb' -print0 | xargs -0 -n1 ruby -c diff --git a/.github/workflows/rust-lint.yml b/.github/workflows/rust-lint.yml new file mode 100644 index 0000000..da00680 --- /dev/null +++ b/.github/workflows/rust-lint.yml @@ -0,0 +1,33 @@ +name: Lint Rust Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/rust/**, .github/workflows/rust-lint.yml] + pull_request: + paths: [frameworks/web-server/rust/**, .github/workflows/rust-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Rust format and clippy + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-rust + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@1.88.0 + with: + components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Check formatting and clippy + shell: bash + run: | + find frameworks/web-server/rust -name Cargo.toml -print0 | + xargs -0 -n1 sh -c 'cargo fmt --manifest-path "$0" -- --check && cargo clippy --manifest-path "$0" --all-targets -- -D warnings' diff --git a/.github/workflows/zig-lint.yml b/.github/workflows/zig-lint.yml new file mode 100644 index 0000000..3f7888b --- /dev/null +++ b/.github/workflows/zig-lint.yml @@ -0,0 +1,29 @@ +name: Lint Zig Frameworks + +on: + push: + branches: [main] + paths: [frameworks/web-server/zig/**, .github/workflows/zig-lint.yml] + pull_request: + paths: [frameworks/web-server/zig/**, .github/workflows/zig-lint.yml] + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Zig format + runs-on: ubuntu-24.04 + timeout-minutes: 10 + environment: + name: lint-zig + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + - name: Check formatting + run: find frameworks/web-server/zig -name '*.zig' -print0 | xargs -0 zig fmt --check diff --git a/.gitignore b/.gitignore index 7234f35..ca82afe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build artifacts -/target/ +target/ +__pycache__/ # Operating System .DS_Store @@ -12,3 +13,6 @@ results/raw/ # Environment .env .env.local + +# Local shell environment +.direnv/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e417493..8cb1a27 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: hooks: - id: convention-audit name: check repository conventions - entry: pwsh -File scripts/check_conventions.ps1 + entry: sh scripts/check_conventions.sh language: system pass_filenames: false always_run: true diff --git a/crates/collector/src/aggregator.rs b/crates/collector/src/aggregator.rs index e1801a4..82b1e1c 100644 --- a/crates/collector/src/aggregator.rs +++ b/crates/collector/src/aggregator.rs @@ -3,7 +3,8 @@ use common::result::ResourceUsage; #[derive(Debug, Default, Clone)] pub struct HarvestAggregator { pub peak_rss_bytes: u64, - pub samples: Vec, + pub rss_sum_bytes: u64, + pub sample_count: u64, pub cpu_user_pct: f32, pub cpu_kernel_pct: f32, pub context_switches: u64, @@ -14,15 +15,15 @@ impl HarvestAggregator { if rss_bytes > self.peak_rss_bytes { self.peak_rss_bytes = rss_bytes; } - self.samples.push(rss_bytes); + self.rss_sum_bytes = self.rss_sum_bytes.saturating_add(rss_bytes); + self.sample_count += 1; } pub fn finalize(self) -> ResourceUsage { - let avg_rss = if self.samples.is_empty() { + let avg_rss = if self.sample_count == 0 { 0 } else { - let sum: u64 = self.samples.iter().sum(); - sum / (self.samples.len() as u64) + self.rss_sum_bytes / self.sample_count }; ResourceUsage { @@ -31,6 +32,7 @@ impl HarvestAggregator { cpu_user_pct: self.cpu_user_pct, cpu_kernel_pct: self.cpu_kernel_pct, context_switches: self.context_switches, + rapl_joules: None, } } } diff --git a/crates/collector/src/ebpf.rs b/crates/collector/src/ebpf.rs new file mode 100644 index 0000000..29f5fbe --- /dev/null +++ b/crates/collector/src/ebpf.rs @@ -0,0 +1,32 @@ +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct EbpfMetrics { + pub off_cpu_usec: u64, + pub page_cache_hits: u64, + pub page_cache_misses: u64, + pub context_switches: u64, +} + +pub fn parse_ebpf_metrics(content: &str) -> EbpfMetrics { + let mut metrics = EbpfMetrics::default(); + for line in content.lines() { + let Some((name, value)) = line.split_once(' ') else { + continue; + }; + let Ok(value) = value.trim().parse() else { + continue; + }; + match name { + "off_cpu_usec" => metrics.off_cpu_usec = value, + "page_cache_hits" => metrics.page_cache_hits = value, + "page_cache_misses" => metrics.page_cache_misses = value, + "context_switches" => metrics.context_switches = value, + _ => (), + } + } + metrics +} + +pub fn page_cache_hit_ratio(metrics: EbpfMetrics) -> Option { + let total = metrics.page_cache_hits + metrics.page_cache_misses; + (total > 0).then(|| metrics.page_cache_hits as f64 / total as f64) +} diff --git a/crates/collector/src/lib.rs b/crates/collector/src/lib.rs index 7c760f0..55132ea 100644 --- a/crates/collector/src/lib.rs +++ b/crates/collector/src/lib.rs @@ -1,11 +1,17 @@ pub mod aggregator; pub mod cgroups; +pub mod ebpf; pub mod error; +pub mod perf; pub mod procfs; +pub mod rapl; pub mod sampler; pub use aggregator::HarvestAggregator; pub use cgroups::{CgroupSample, parse_cgroup_cpu_stat, parse_cgroup_memory_stat}; +pub use ebpf::{EbpfMetrics, page_cache_hit_ratio, parse_ebpf_metrics}; pub use error::CollectorError; +pub use perf::{PerfMode, parse_perf_event_paranoid, parse_perf_stat_value, select_perf_mode}; pub use procfs::{ProcessSample, parse_statm, parse_status_switches}; +pub use rapl::{RaplZoneSnapshot, energy_delta_joules, energy_delta_uj, read_rapl_snapshot}; pub use sampler::BackgroundSampler; diff --git a/crates/collector/src/perf.rs b/crates/collector/src/perf.rs new file mode 100644 index 0000000..978b80e --- /dev/null +++ b/crates/collector/src/perf.rs @@ -0,0 +1,31 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PerfMode { + Hardware, + SoftwareFallback, + Unavailable, +} + +pub fn parse_perf_event_paranoid(content: &str) -> Option { + content.trim().parse().ok() +} + +pub fn select_perf_mode(paranoid: Option, has_perfmon_cap: bool) -> PerfMode { + if has_perfmon_cap || paranoid.is_some_and(|value| value <= 1) { + return PerfMode::Hardware; + } + if paranoid.is_some() { + return PerfMode::SoftwareFallback; + } + PerfMode::Unavailable +} + +pub fn parse_perf_stat_value(content: &str) -> Option { + content + .trim() + .split(',') + .next() + .map(str::trim) + .filter(|value| *value != "" && *value != "")? + .parse() + .ok() +} diff --git a/crates/collector/src/rapl.rs b/crates/collector/src/rapl.rs new file mode 100644 index 0000000..6ea63a2 --- /dev/null +++ b/crates/collector/src/rapl.rs @@ -0,0 +1,90 @@ +use crate::error::CollectorError; +use std::fs; +use std::path::Path; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RaplZoneSnapshot { + pub name: String, + pub energy_uj: u64, + pub max_energy_range_uj: u64, +} + +pub fn read_rapl_snapshot(root: &Path) -> Result, CollectorError> { + let entries = match fs::read_dir(root) { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), + Err(error) => return Err(error.into()), + }; + let mut zones = Vec::new(); + for entry in entries { + let entry = entry?; + let path = entry.path(); + if !path.is_dir() { + continue; + } + read_rapl_directory(&path, &mut zones)?; + } + zones.sort_unstable_by(|left, right| left.name.cmp(&right.name)); + Ok(zones) +} + +fn read_rapl_directory( + path: &Path, + zones: &mut Vec, +) -> Result<(), CollectorError> { + if path.join("energy_uj").is_file() { + zones.push(read_rapl_zone(path)?); + } + for entry in fs::read_dir(path)? { + let path = entry?.path(); + if path.is_dir() { + read_rapl_directory(&path, zones)?; + } + } + Ok(()) +} + +pub fn energy_delta_uj(before: u64, after: u64, max_range: u64) -> u64 { + if after >= before { + return after - before; + } + max_range.saturating_sub(before).saturating_add(after) +} + +pub fn energy_delta_joules(before: &[RaplZoneSnapshot], after: &[RaplZoneSnapshot]) -> f64 { + after + .iter() + .filter_map(|end| { + before + .iter() + .find(|start| start.name == end.name) + .map(|start| { + energy_delta_uj(start.energy_uj, end.energy_uj, start.max_energy_range_uj) + }) + }) + .sum::() as f64 + / 1_000_000.0 +} + +fn read_rapl_zone(path: &Path) -> Result { + let label = fs::read_to_string(path.join("name"))?.trim().to_string(); + let id = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("unknown"); + let name = format!("{id}:{label}"); + let energy_uj = read_u64(path, "energy_uj")?; + let max_energy_range_uj = read_u64(path, "max_energy_range_uj")?; + Ok(RaplZoneSnapshot { + name, + energy_uj, + max_energy_range_uj, + }) +} + +fn read_u64(path: &Path, name: &str) -> Result { + fs::read_to_string(path.join(name))? + .trim() + .parse() + .map_err(|error| CollectorError::Parse(format!("invalid RAPL {name}: {error}"))) +} diff --git a/crates/collector/src/sampler.rs b/crates/collector/src/sampler.rs index 3342d8c..fdd3569 100644 --- a/crates/collector/src/sampler.rs +++ b/crates/collector/src/sampler.rs @@ -1,9 +1,11 @@ use crate::aggregator::HarvestAggregator; use crate::cgroups::parse_cgroup_memory_stat; use crate::procfs::parse_statm; +use crate::rapl::{RaplZoneSnapshot, energy_delta_joules, read_rapl_snapshot}; use common::result::ResourceUsage; -use std::fs; -use std::path::{Path, PathBuf}; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; +use std::path::Path; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; @@ -15,12 +17,19 @@ pub struct BackgroundSampler { running: Arc, aggregator: Arc>, handle: Option>, + rapl_start: Vec, +} + +#[derive(Debug)] +struct RssSource { + file: Option, + is_cgroup: bool, } impl BackgroundSampler { pub fn spawn_target( target_pid: Option, - cgroup_dir: Option, + cgroup_dir: Option<&Path>, interval: Duration, ) -> Self { let running = Arc::new(AtomicBool::new(true)); @@ -29,11 +38,13 @@ impl BackgroundSampler { let run_flag = Arc::clone(&running); let agg_lock = Arc::clone(&aggregator); + let mut source = open_rss_source(target_pid, cgroup_dir); + let rapl_start = read_rapl_snapshot(Path::new("/sys/class/powercap")).unwrap_or_default(); let handle = tokio::spawn(async move { let mut ticker = tokio::time::interval(interval); while run_flag.load(Ordering::Relaxed) { ticker.tick().await; - let sample_rss = sample_rss_bytes(target_pid, cgroup_dir.as_deref()); + let sample_rss = sample_rss_bytes(&mut source); let mut agg = agg_lock.lock().await; agg.record_sample(sample_rss); } @@ -43,6 +54,7 @@ impl BackgroundSampler { running, aggregator, handle: Some(handle), + rapl_start, } } @@ -66,6 +78,7 @@ impl BackgroundSampler { running, aggregator, handle: Some(handle), + rapl_start: Vec::new(), } } @@ -75,27 +88,50 @@ impl BackgroundSampler { let _ = h.await; } let agg = self.aggregator.lock().await; - agg.clone().finalize() + let mut usage = agg.clone().finalize(); + let rapl_end = read_rapl_snapshot(Path::new("/sys/class/powercap")).unwrap_or_default(); + if !self.rapl_start.is_empty() && !rapl_end.is_empty() { + usage.rapl_joules = Some(energy_delta_joules(&self.rapl_start, &rapl_end)); + } + usage } } -fn sample_rss_bytes(target_pid: Option, cgroup_dir: Option<&Path>) -> u64 { +fn open_rss_source(target_pid: Option, cgroup_dir: Option<&Path>) -> RssSource { if let Some(cg) = cgroup_dir { let mem_path = cg.join("memory.current"); - if let Ok(content) = fs::read_to_string(mem_path) { - if let Ok(bytes) = parse_cgroup_memory_stat(&content) { - return bytes; - } + if let Ok(file) = File::open(mem_path) { + return RssSource { + file: Some(file), + is_cgroup: true, + }; } } let pid_str = target_pid.map_or_else(|| "self".to_string(), |p| p.to_string()); let statm_path = format!("/proc/{pid_str}/statm"); - if let Ok(content) = fs::read_to_string(&statm_path) { - if let Ok(bytes) = parse_statm(&content, 4096) { - return bytes; - } + RssSource { + file: File::open(statm_path).ok(), + is_cgroup: false, } +} - 0 +fn sample_rss_bytes(source: &mut RssSource) -> u64 { + let Some(file) = source.file.as_mut() else { + return 0; + }; + let mut buffer = [0u8; 128]; + if file.seek(SeekFrom::Start(0)).is_err() { + return 0; + } + let Ok(count) = file.read(&mut buffer) else { + return 0; + }; + let Ok(content) = std::str::from_utf8(&buffer[..count]) else { + return 0; + }; + if source.is_cgroup { + return parse_cgroup_memory_stat(content).unwrap_or(0); + } + parse_statm(content, 4096).unwrap_or(0) } diff --git a/crates/collector/tests/collector_test.rs b/crates/collector/tests/collector_test.rs index fe63a63..31bab17 100644 --- a/crates/collector/tests/collector_test.rs +++ b/crates/collector/tests/collector_test.rs @@ -1,6 +1,10 @@ use collector::aggregator::HarvestAggregator; use collector::cgroups::{parse_cgroup_cpu_stat, parse_cgroup_memory_stat}; use collector::procfs::{parse_statm, parse_status_switches}; +use collector::{ + EbpfMetrics, PerfMode, energy_delta_uj, page_cache_hit_ratio, parse_ebpf_metrics, + parse_perf_event_paranoid, select_perf_mode, +}; #[test] fn test_parse_procfs_statm() { @@ -46,3 +50,24 @@ fn test_harvest_aggregator_metrics() { assert!((res.cpu_user_pct - 65.0).abs() < f32::EPSILON); assert_eq!(res.context_switches, 2000); } + +#[test] +fn test_energy_counter_wrap() { + assert_eq!(energy_delta_uj(90, 20, 100), 30); + assert_eq!(energy_delta_uj(20, 90, 100), 70); +} + +#[test] +fn test_perf_fallback_selection() { + assert_eq!(parse_perf_event_paranoid("2\n"), Some(2)); + assert_eq!(select_perf_mode(Some(2), false), PerfMode::SoftwareFallback); + assert_eq!(select_perf_mode(Some(3), true), PerfMode::Hardware); +} + +#[test] +fn test_ebpf_metric_parsing() { + let metrics = parse_ebpf_metrics("off_cpu_usec 10\npage_cache_hits 4\npage_cache_misses 1\n"); + assert_eq!(metrics.off_cpu_usec, 10); + assert_eq!(page_cache_hit_ratio(metrics), Some(0.8)); + assert_eq!(page_cache_hit_ratio(EbpfMetrics::default()), None); +} diff --git a/crates/common/src/manifest.rs b/crates/common/src/manifest.rs index cea42a9..400836c 100644 --- a/crates/common/src/manifest.rs +++ b/crates/common/src/manifest.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct FrameworkMetadata { pub name: String, pub version: String, @@ -14,6 +15,7 @@ pub struct FrameworkMetadata { } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Capabilities { #[serde(default)] pub plaintext: bool, @@ -36,6 +38,7 @@ pub struct Capabilities { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DockerBuildConfig { pub dockerfile_glibc: Option, pub dockerfile_musl: Option, @@ -44,12 +47,14 @@ pub struct DockerBuildConfig { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct NixBuildConfig { pub flake_attr_glibc: Option, pub flake_attr_musl: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct BuildConfig { pub engine: String, pub docker: Option, @@ -57,6 +62,7 @@ pub struct BuildConfig { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct RuntimeConfig { pub port: u16, pub healthcheck_path: String, @@ -72,6 +78,7 @@ fn default_startup_timeout() -> u64 { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct FrameworkManifest { pub framework: FrameworkMetadata, pub capabilities: Capabilities, @@ -109,5 +116,29 @@ pub fn validate_manifest(m: &FrameworkManifest) -> Result<(), CommonError> { "build engine must be 'docker' or 'nix'".into(), )); } + if m.build.engine == "docker" { + let Some(docker) = &m.build.docker else { + return Err(CommonError::Validation( + "docker engine requires docker config".into(), + )); + }; + if docker.dockerfile_glibc.is_none() || docker.dockerfile_musl.is_none() { + return Err(CommonError::Validation( + "docker engine requires glibc and musl Dockerfiles".into(), + )); + } + } + if m.build.engine == "nix" { + let Some(nix) = &m.build.nix else { + return Err(CommonError::Validation( + "nix engine requires nix config".into(), + )); + }; + if nix.flake_attr_glibc.is_none() || nix.flake_attr_musl.is_none() { + return Err(CommonError::Validation( + "nix engine requires glibc and musl flake attributes".into(), + )); + } + } Ok(()) } diff --git a/crates/common/src/result.rs b/crates/common/src/result.rs index 8d49607..0131528 100644 --- a/crates/common/src/result.rs +++ b/crates/common/src/result.rs @@ -39,6 +39,8 @@ pub struct ResourceUsage { pub cpu_user_pct: f32, pub cpu_kernel_pct: f32, pub context_switches: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub rapl_joules: Option, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/common/tests/result_test.rs b/crates/common/tests/result_test.rs index c98a4a6..ce145ae 100644 --- a/crates/common/tests/result_test.rs +++ b/crates/common/tests/result_test.rs @@ -44,6 +44,7 @@ fn test_roundtrip_result_serialization() { cpu_user_pct: 75.5, cpu_kernel_pct: 12.3, context_switches: 45_000, + rapl_joules: None, }, status_codes: StatusCodeCounts { http_2xx: 3_000_000, diff --git a/crates/load-gen/src/autobahn/builder.rs b/crates/load-gen/src/autobahn/builder.rs index 7a501fe..caf7d64 100644 --- a/crates/load-gen/src/autobahn/builder.rs +++ b/crates/load-gen/src/autobahn/builder.rs @@ -66,6 +66,30 @@ impl FrameBuilder { frames } + pub fn build_fragmented_binary(data: &[u8], chunks: &[usize], mask: [u8; 4]) -> Vec> { + let mut frames = Vec::with_capacity(chunks.len()); + let mut offset = 0usize; + for (index, size) in chunks.iter().enumerate() { + let end = offset.saturating_add(*size).min(data.len()); + let opcode = if index == 0 { + FrameOpcode::Binary + } else { + FrameOpcode::Continuation + }; + frames.push(Self::build_client_frame( + opcode, + &data[offset..end], + mask, + end == data.len(), + )); + offset = end; + if offset == data.len() { + break; + } + } + frames + } + pub fn build_close_frame(code: u16, mask: [u8; 4]) -> Vec { let payload = code.to_be_bytes(); Self::build_client_frame(FrameOpcode::Close, &payload, mask, true) diff --git a/crates/load-gen/src/client.rs b/crates/load-gen/src/client.rs index f9b99d0..19c6661 100644 --- a/crates/load-gen/src/client.rs +++ b/crates/load-gen/src/client.rs @@ -1,14 +1,14 @@ use crate::error::LoadGenError; use crate::histogram::LatencyRecorder; use crate::stats::WorkerStats; -use std::time::Instant; +use std::time::{Duration, Instant}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; struct WorkerTask<'a> { deadline: Instant, pipeline_depth: usize, - expected_interval_us: u64, + expected_interval: Option, recorder: &'a mut LatencyRecorder, stats: &'a mut WorkerStats, } @@ -25,26 +25,26 @@ pub async fn run_connection_worker( let mut task = WorkerTask { deadline, pipeline_depth, - expected_interval_us, + expected_interval: (expected_interval_us > 0) + .then(|| Duration::from_micros(expected_interval_us)), recorder, stats, }; - let mut read_buf = vec![0u8; 16384]; + let mut read_buf = Vec::with_capacity(32_768); while Instant::now() < task.deadline { - let Ok(stream) = TcpStream::connect(target_addr).await else { + let Ok(mut stream) = TcpStream::connect(target_addr).await else { task.stats.record_error(); tokio::time::sleep(std::time::Duration::from_millis(10)).await; continue; }; let _ = stream.set_nodelay(true); - let mut stream = stream; - - let res = - process_connection_loop(&mut stream, request_bytes, &mut read_buf, &mut task).await; - if res.is_err() { + if process_connection_loop(&mut stream, request_bytes, &mut read_buf, &mut task) + .await + .is_err() + { task.stats.record_error(); } } @@ -54,49 +54,102 @@ pub async fn run_connection_worker( async fn process_connection_loop( stream: &mut TcpStream, request_bytes: &[u8], - read_buf: &mut [u8], + read_buf: &mut Vec, task: &mut WorkerTask<'_>, ) -> Result<(), LoadGenError> { let depth = task.pipeline_depth.max(1); + let mut next_issue = Instant::now(); + let mut starts = Vec::with_capacity(depth); while Instant::now() < task.deadline { - let req_start = Instant::now(); + wait_until(next_issue).await; + if Instant::now() >= task.deadline { + return Ok(()); + } + + starts.clear(); for _ in 0..depth { + let req_start = Instant::now(); stream.write_all(request_bytes).await?; + starts.push(req_start); + if let Some(interval) = task.expected_interval { + next_issue += interval; + } } - for _ in 0..depth { - let n = stream.read(read_buf).await?; + let mut completed = 0; + while completed < depth { + let before = read_buf.len(); + read_buf.resize(before + 16_384, 0); + let n = stream.read(&mut read_buf[before..]).await?; + read_buf.truncate(before + n); if n == 0 { return Ok(()); } + completed += record_responses(read_buf, &starts[completed..], task)?; + } + } + Ok(()) +} - let elapsed_us = req_start.elapsed().as_micros() as u64; - task.recorder - .record_micros_corrected(elapsed_us, task.expected_interval_us); +async fn wait_until(when: Instant) { + if when > Instant::now() { + tokio::time::sleep_until(tokio::time::Instant::from_std(when)).await; + } +} - let mut headers = [httparse::EMPTY_HEADER; 16]; - let mut res = httparse::Response::new(&mut headers); - let code = match res.parse(&read_buf[..n]) { - Ok(httparse::Status::Complete(_)) => res.code.unwrap_or(200), - _ => 200, - }; +fn record_responses( + bytes: &mut Vec, + starts: &[Instant], + task: &mut WorkerTask<'_>, +) -> Result { + let mut offset = 0; + let mut completed = 0; - task.stats.record_response(code, n); + while completed < starts.len() { + let Some((status, response_len)) = parse_response(&bytes[offset..])? else { + break; + }; + let elapsed = starts[completed].elapsed().as_micros() as u64; + let interval = task + .expected_interval + .map_or(0, |value| value.as_micros() as u64); + task.recorder.record_micros_corrected(elapsed, interval); + task.stats.record_response(status, response_len); + offset += response_len; + completed += 1; + } + if offset > 0 { + bytes.drain(..offset); + } + Ok(completed) +} - let mut should_close = false; - for h in &headers { - if h.name.eq_ignore_ascii_case("connection") - && h.value.eq_ignore_ascii_case(b"close") - { - should_close = true; - break; - } - } - if should_close { - return Ok(()); - } - } +fn parse_response(bytes: &[u8]) -> Result, LoadGenError> { + let mut headers = [httparse::EMPTY_HEADER; 16]; + let (header_len, status) = { + let mut response = httparse::Response::new(&mut headers); + let httparse::Status::Complete(header_len) = response + .parse(bytes) + .map_err(|error| LoadGenError::Protocol(error.to_string()))? + else { + return Ok(None); + }; + (header_len, response.code.unwrap_or(200)) + }; + let body_len = headers + .iter() + .find(|header| header.name.eq_ignore_ascii_case("content-length")) + .map_or(Ok(0), |header| { + std::str::from_utf8(header.value) + .map_err(|error| LoadGenError::Protocol(error.to_string()))? + .trim() + .parse::() + .map_err(|error| LoadGenError::Protocol(error.to_string())) + })?; + let response_len = header_len + body_len; + if bytes.len() < response_len { + return Ok(None); } - Ok(()) + Ok(Some((status, response_len))) } diff --git a/crates/load-gen/tests/autobahn_test.rs b/crates/load-gen/tests/autobahn_test.rs index 8d04c84..5bb62d3 100644 --- a/crates/load-gen/tests/autobahn_test.rs +++ b/crates/load-gen/tests/autobahn_test.rs @@ -37,3 +37,14 @@ fn test_fuzz_generators() { let frag_ping = FuzzInjector::create_fragmented_ping(); assert_eq!(frag_ping[0] & 0x80, 0); } + +#[test] +fn test_fragmentation_boundaries() { + let data = vec![7; 64 * 1024]; + let frames = + FrameBuilder::build_fragmented_binary(&data, &[64, 125, 126, 1024, 16384, 65536], [1; 4]); + assert_eq!(frames.len(), 6); + assert_eq!(frames[0][0] & 0x0f, FrameOpcode::Binary as u8); + assert_eq!(frames[1][0] & 0x0f, FrameOpcode::Continuation as u8); + assert_ne!(frames.last().unwrap()[0] & 0x80, 0); +} diff --git a/crates/orchestrator/src/container.rs b/crates/orchestrator/src/container.rs new file mode 100644 index 0000000..76db3a0 --- /dev/null +++ b/crates/orchestrator/src/container.rs @@ -0,0 +1,108 @@ +use crate::error::OrchestratorError; +use common::manifest::FrameworkManifest; +use common::result::LibcVariant; +use std::env; +use std::path::Path; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Debug, Clone)] +pub struct ContainerTarget { + pub name: String, + pub pid: u32, +} + +pub fn start_container( + framework_dir: &Path, + manifest: &FrameworkManifest, + libc: LibcVariant, + database_url: Option<&str>, +) -> Result { + let docker = + manifest.build.docker.as_ref().ok_or_else(|| { + OrchestratorError::Process("framework has no Docker build config".into()) + })?; + let dockerfile = match libc { + LibcVariant::Glibc => docker.dockerfile_glibc.as_deref(), + LibcVariant::Musl => docker.dockerfile_musl.as_deref(), + } + .ok_or_else(|| OrchestratorError::Process("framework lacks requested libc image".into()))?; + let suffix = match libc { + LibcVariant::Glibc => "glibc", + LibcVariant::Musl => "musl", + }; + let stamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|error| OrchestratorError::Process(error.to_string()))? + .as_millis(); + let base = sanitize_name(&manifest.framework.name); + let image = format!("framework-benchmark/{base}:{suffix}"); + let name = format!("benchmark-{base}-{suffix}-{stamp}"); + let workers = benchmark_workers(); + + run_docker( + framework_dir, + &["build", "--file", dockerfile, "--tag", &image, "."], + )?; + let mut args = vec![ + "run".into(), + "--detach".into(), + "--name".into(), + name.clone(), + "--label".into(), + "framework-benchmark.managed=true".into(), + "--network".into(), + "host".into(), + "--env".into(), + format!("BENCHMARK_WORKERS={workers}"), + ]; + if let Some(database_url) = database_url { + args.push("--env".into()); + args.push(format!("DATABASE_URL={database_url}")); + } + args.push(image); + let output = run_docker(framework_dir, &args)?; + let id = output.trim(); + let pid = run_docker(framework_dir, &["inspect", "--format={{.State.Pid}}", id])? + .trim() + .parse() + .map_err(|error| OrchestratorError::Process(format!("invalid container pid: {error}")))?; + Ok(ContainerTarget { name, pid }) +} + +pub fn stop_container(framework_dir: &Path, target: &ContainerTarget) { + let _ = run_docker(framework_dir, &["rm", "--force", &target.name]); +} + +fn benchmark_workers() -> usize { + let configured = env::var("BENCHMARK_WORKERS") + .ok() + .and_then(|value| value.parse().ok()) + .filter(|value: &usize| *value > 0); + configured.unwrap_or_else(|| std::thread::available_parallelism().map_or(1, usize::from)) +} + +fn run_docker>(cwd: &Path, args: &[S]) -> Result { + let output = Command::new("docker") + .args(args.iter().map(AsRef::as_ref)) + .current_dir(cwd) + .output()?; + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).into_owned()); + } + Err(OrchestratorError::Process( + String::from_utf8_lossy(&output.stderr).trim().to_string(), + )) +} + +fn sanitize_name(name: &str) -> String { + name.chars() + .map(|char| { + if char.is_ascii_alphanumeric() { + char + } else { + '-' + } + }) + .collect() +} diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index dbcff80..392035f 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -1,13 +1,16 @@ pub mod discovery; pub mod error; pub mod lifecycle; +pub mod machine; pub mod poller; pub mod runner; pub mod ws_bench; +pub use container::{ContainerTarget, start_container, stop_container}; pub use discovery::discover_frameworks; pub use error::OrchestratorError; pub use lifecycle::{run_framework_suite, run_framework_test}; pub use poller::wait_for_ready; pub use runner::{BenchmarkSessionConfig, execute_framework_session}; pub use ws_bench::execute_websocket_workload; +pub mod container; diff --git a/crates/orchestrator/src/lifecycle.rs b/crates/orchestrator/src/lifecycle.rs index 447814d..bb8c57c 100644 --- a/crates/orchestrator/src/lifecycle.rs +++ b/crates/orchestrator/src/lifecycle.rs @@ -1,11 +1,47 @@ use collector::BackgroundSampler; use common::manifest::FrameworkManifest; -use common::result::{LibcVariant, MachineInfo, RunResult, TestMetrics}; +use common::result::{LibcVariant, RunResult, TestMetrics}; use load_gen::engine::execute_benchmark; use load_gen::plan::BenchmarkPlan; use std::path::Path; use std::time::Duration; +#[derive(Debug, Clone, Copy)] +pub struct SuiteTiming { + pub warmup_secs: u64, + pub cooldown_secs: u64, +} + +impl Default for SuiteTiming { + fn default() -> Self { + Self { + warmup_secs: 15, + cooldown_secs: 10, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SuiteExecutionConfig<'a> { + pub target_addr: &'a str, + pub concurrency: u32, + pub duration_secs: u32, + pub libc: LibcVariant, + pub target_pid: Option, + pub cgroup_dir: Option<&'a Path>, + pub timing: SuiteTiming, +} + +#[derive(Clone, Copy)] +struct WorkloadConfig<'a> { + target_addr: &'a str, + concurrency: u32, + duration_secs: u32, + target_pid: Option, + cgroup_dir: Option<&'a Path>, + timing: SuiteTiming, +} + pub async fn run_framework_test( manifest: &FrameworkManifest, target_addr: &str, @@ -15,24 +51,22 @@ pub async fn run_framework_test( duration_secs: u32, libc: LibcVariant, ) -> Result { - let metrics = execute_single_workload( + let config = WorkloadConfig { target_addr, - category, - endpoint_path, concurrency, duration_secs, - None, - None, - ) - .await?; - + target_pid: None, + cgroup_dir: None, + timing: SuiteTiming::default(), + }; + let metrics = execute_single_workload(config, category, endpoint_path).await?; Ok(RunResult { run_id: "test-run-1".into(), framework_name: manifest.framework.name.clone(), framework_version: manifest.framework.version.clone(), libc, - timestamp_epoch_secs: 1_700_000_000, - machine: default_machine_info(), + timestamp_epoch_secs: crate::machine::current_epoch_secs(), + machine: crate::machine::current_machine_info(), benchmarks: vec![metrics], }) } @@ -45,10 +79,25 @@ pub async fn run_framework_suite( libc: LibcVariant, target_pid: Option, cgroup_dir: Option<&Path>, +) -> Result { + let config = SuiteExecutionConfig { + target_addr, + concurrency, + duration_secs, + libc, + target_pid, + cgroup_dir, + timing: SuiteTiming::default(), + }; + run_framework_suite_with_timing(manifest, config).await +} + +pub async fn run_framework_suite_with_timing( + manifest: &FrameworkManifest, + config: SuiteExecutionConfig<'_>, ) -> Result { let mut benchmarks = Vec::new(); let caps = &manifest.capabilities; - let endpoints = [ (caps.plaintext, "plaintext", "/plaintext"), (caps.json, "json", "/json"), @@ -60,86 +109,86 @@ pub async fn run_framework_suite( for (enabled, cat, path) in endpoints { if enabled { - benchmarks.push( - execute_single_workload( - target_addr, - cat, - path, - concurrency, - duration_secs, - target_pid, - cgroup_dir, - ) - .await?, - ); + benchmarks + .push(execute_single_workload(WorkloadConfig::from(config), cat, path).await?); } } if caps.websocket { benchmarks.push( crate::ws_bench::execute_websocket_workload( - target_addr, + config.target_addr, "/ws", - concurrency, - duration_secs, - target_pid, - cgroup_dir, + config.concurrency, + config.duration_secs, + config.target_pid, + config.cgroup_dir, ) .await?, ); } Ok(RunResult { - run_id: format!("{}-suite", manifest.framework.name), + run_id: format!("{}-{:?}-suite", manifest.framework.name, config.libc).to_lowercase(), framework_name: manifest.framework.name.clone(), framework_version: manifest.framework.version.clone(), - libc, - timestamp_epoch_secs: 1_700_000_000, - machine: default_machine_info(), + libc: config.libc, + timestamp_epoch_secs: crate::machine::current_epoch_secs(), + machine: crate::machine::current_machine_info(), benchmarks, }) } +impl<'a> From> for WorkloadConfig<'a> { + fn from(config: SuiteExecutionConfig<'a>) -> Self { + Self { + target_addr: config.target_addr, + concurrency: config.concurrency, + duration_secs: config.duration_secs, + target_pid: config.target_pid, + cgroup_dir: config.cgroup_dir, + timing: config.timing, + } + } +} + async fn execute_single_workload( - target_addr: &str, + config: WorkloadConfig<'_>, category: &str, endpoint_path: &str, - concurrency: u32, - duration_secs: u32, - target_pid: Option, - cgroup_dir: Option<&Path>, ) -> Result { let warmup_plan = BenchmarkPlan { - target_addr: target_addr.to_string(), + target_addr: config.target_addr.to_string(), path: endpoint_path.to_string(), - concurrency: (concurrency as usize).min(16), - duration_secs: 5, + concurrency: (config.concurrency as usize).min(16), + duration_secs: config.timing.warmup_secs, pipeline_depth: 1, target_rps: None, }; let _ = execute_benchmark(warmup_plan).await?; let sampler = BackgroundSampler::spawn_target( - target_pid, - cgroup_dir.map(Path::to_path_buf), + config.target_pid, + config.cgroup_dir, Duration::from_millis(50), ); let measurement_plan = BenchmarkPlan { - target_addr: target_addr.to_string(), + target_addr: config.target_addr.to_string(), path: endpoint_path.to_string(), - concurrency: concurrency as usize, - duration_secs: u64::from(duration_secs), + concurrency: config.concurrency as usize, + duration_secs: u64::from(config.duration_secs), pipeline_depth: 1, target_rps: None, }; let exec_res = execute_benchmark(measurement_plan).await?; let resources = sampler.stop().await; + tokio::time::sleep(Duration::from_secs(config.timing.cooldown_secs)).await; Ok(TestMetrics { category: category.to_string(), - concurrency, - duration_secs, + concurrency: config.concurrency, + duration_secs: config.duration_secs, requests_total: exec_res.total_requests, requests_per_sec: exec_res.requests_per_sec, transfer_mb_per_sec: exec_res.transfer_mb_per_sec, @@ -148,15 +197,3 @@ async fn execute_single_workload( status_codes: exec_res.stats.to_status_code_counts(), }) } - -fn default_machine_info() -> MachineInfo { - MachineInfo { - hostname: "benchmark-host".into(), - os_release: "linux".into(), - kernel_version: "6.6".into(), - cpu_model: "synthetic".into(), - physical_cores: 8, - logical_cores: 16, - total_memory_mb: 16384, - } -} diff --git a/crates/orchestrator/src/machine.rs b/crates/orchestrator/src/machine.rs new file mode 100644 index 0000000..f5c6084 --- /dev/null +++ b/crates/orchestrator/src/machine.rs @@ -0,0 +1,124 @@ +use common::result::MachineInfo; +use std::collections::BTreeSet; +use std::fs; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn current_machine_info() -> MachineInfo { + let cpuinfo = fs::read_to_string("/proc/cpuinfo").unwrap_or_default(); + MachineInfo { + hostname: hostname(), + os_release: os_release(), + kernel_version: read_trimmed("/proc/sys/kernel/osrelease") + .unwrap_or_else(|| "unknown".into()), + cpu_model: cpu_model(&cpuinfo).unwrap_or_else(|| "unknown".into()), + physical_cores: physical_core_count(&cpuinfo), + logical_cores: logical_core_count(&cpuinfo), + total_memory_mb: memory_mb(), + } +} + +pub fn current_epoch_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_or(0, |duration| duration.as_secs()) +} + +fn hostname() -> String { + std::env::var("HOSTNAME") + .ok() + .filter(|value| !value.is_empty()) + .or_else(|| read_trimmed("/etc/hostname")) + .unwrap_or_else(|| "unknown".into()) +} + +fn os_release() -> String { + let content = fs::read_to_string("/etc/os-release").unwrap_or_default(); + content + .lines() + .find_map(|line| line.strip_prefix("PRETTY_NAME=")) + .map_or_else(|| std::env::consts::OS.into(), unquote) +} + +fn read_trimmed(path: &str) -> Option { + fs::read_to_string(path) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) +} + +fn unquote(value: &str) -> String { + value.trim_matches('"').to_string() +} + +fn cpu_model(cpuinfo: &str) -> Option { + cpuinfo + .lines() + .find_map(|line| { + line.split_once(':') + .filter(|(key, _)| key.trim() == "model name") + }) + .map(|(_, value)| value.trim().to_string()) +} + +fn logical_core_count(cpuinfo: &str) -> usize { + let count = cpuinfo + .lines() + .filter(|line| line.starts_with("processor")) + .count(); + count.max(1) +} + +fn physical_core_count(cpuinfo: &str) -> usize { + let mut cores = BTreeSet::new(); + let mut physical_id: Option = None; + let mut core_id: Option = None; + + for line in cpuinfo.lines().chain(std::iter::once("")) { + if line.is_empty() { + if let (Some(package), Some(core)) = (physical_id.take(), core_id.take()) { + cores.insert((package, core)); + } + continue; + } + let Some((key, value)) = line.split_once(':') else { + continue; + }; + match key.trim() { + "physical id" => physical_id = value.trim().parse().ok(), + "core id" => core_id = value.trim().parse().ok(), + _ => {} + } + } + if cores.is_empty() { + logical_core_count(cpuinfo) + } else { + cores.len() + } +} + +fn memory_mb() -> u64 { + fs::read_to_string("/proc/meminfo") + .ok() + .and_then(|content| { + content.lines().find_map(|line| { + line.strip_prefix("MemTotal:")? + .split_whitespace() + .next()? + .parse::() + .ok() + }) + }) + .map_or(0, |kb| kb / 1024) +} + +#[cfg(test)] +mod tests { + use super::{cpu_model, physical_core_count}; + + #[test] + fn parses_cpu_layout() { + let cpuinfo = "processor : 0\nphysical id : 0\ncore id : 0\n\nprocessor : 1\nphysical id : 0\ncore id : 0\n\nprocessor : 2\nphysical id : 0\ncore id : 1\nmodel name : test cpu\n"; + assert_eq!(physical_core_count(cpuinfo), 2); + assert_eq!(cpu_model(cpuinfo).as_deref(), Some("test cpu")); + } +} diff --git a/crates/orchestrator/src/main.rs b/crates/orchestrator/src/main.rs index 4e9f86e..f0a7012 100644 --- a/crates/orchestrator/src/main.rs +++ b/crates/orchestrator/src/main.rs @@ -1,3 +1,4 @@ +use common::result::LibcVariant; use orchestrator::discovery::discover_frameworks; use orchestrator::error::OrchestratorError; use orchestrator::runner::{BenchmarkSessionConfig, execute_framework_session}; @@ -23,8 +24,11 @@ async fn main() -> Result<(), OrchestratorError> { println!("==> Discovered {} framework targets.", frameworks.len()); let config = BenchmarkSessionConfig { output_dir: output_dir.to_string(), + database_url: env::var("DATABASE_URL").ok(), + manage_container: env::var("BENCHMARK_MANAGE_CONTAINER").map_or(true, |value| value != "0"), ..BenchmarkSessionConfig::default() }; + let libcs = selected_libcs()?; for (dir, manifest) in frameworks { println!( @@ -33,20 +37,38 @@ async fn main() -> Result<(), OrchestratorError> { manifest.framework.category, dir.display() ); - match execute_framework_session(&manifest, &config).await { - Ok(res) => { - let rps = res.benchmarks.first().map_or(0.0, |b| b.requests_per_sec); - println!( - "==> Benchmark completed for {}: {:.2} req/s across {} workloads", - manifest.framework.name, - rps, - res.benchmarks.len() - ); + for libc in &libcs { + let config = BenchmarkSessionConfig { + libc: *libc, + ..config.clone() + }; + match execute_framework_session(&dir, &manifest, &config).await { + Ok(res) => { + let rps = res.benchmarks.first().map_or(0.0, |b| b.requests_per_sec); + println!( + "==> Benchmark completed for {}: {:.2} req/s across {} workloads", + manifest.framework.name, + rps, + res.benchmarks.len() + ); + } + Err(e) => eprintln!("==> Failed benchmark for {}: {e}", manifest.framework.name), } - Err(e) => eprintln!("==> Failed benchmark for {}: {e}", manifest.framework.name), } } println!("==> Benchmark orchestration finished."); Ok(()) } + +fn selected_libcs() -> Result, OrchestratorError> { + let selected = env::var("BENCHMARK_LIBC").unwrap_or_else(|_| "all".into()); + match selected.as_str() { + "all" => Ok(vec![LibcVariant::Glibc, LibcVariant::Musl]), + "glibc" => Ok(vec![LibcVariant::Glibc]), + "musl" => Ok(vec![LibcVariant::Musl]), + value => Err(OrchestratorError::Process(format!( + "invalid BENCHMARK_LIBC: {value}" + ))), + } +} diff --git a/crates/orchestrator/src/runner.rs b/crates/orchestrator/src/runner.rs index 07631a6..1b4c259 100644 --- a/crates/orchestrator/src/runner.rs +++ b/crates/orchestrator/src/runner.rs @@ -1,6 +1,7 @@ use crate::error::OrchestratorError; use crate::lifecycle::run_framework_suite; use crate::poller::wait_for_ready; +use crate::{start_container, stop_container}; use common::manifest::FrameworkManifest; use common::result::{LibcVariant, RunResult}; use reporter::archive::write_result_json; @@ -21,6 +22,8 @@ pub struct BenchmarkSessionConfig { pub readme_path: Option, pub target_pid: Option, pub cgroup_dir: Option, + pub database_url: Option, + pub manage_container: bool, } impl Default for BenchmarkSessionConfig { @@ -28,47 +31,67 @@ impl Default for BenchmarkSessionConfig { Self { target_host: "127.0.0.1".into(), concurrency: 64, - duration_secs: 15, + duration_secs: 30, libc: LibcVariant::Glibc, output_dir: "results".into(), readme_path: Some("README.md".into()), target_pid: None, cgroup_dir: None, + database_url: None, + manage_container: true, } } } pub async fn execute_framework_session( + framework_dir: &Path, manifest: &FrameworkManifest, config: &BenchmarkSessionConfig, ) -> Result { + let target = if config.manage_container { + Some(start_container( + framework_dir, + manifest, + config.libc, + config.database_url.as_deref(), + )?) + } else { + None + }; let port = manifest.runtime.port; let target_addr = format!("{}:{}", config.target_host, port); - wait_for_ready( - &target_addr, - &manifest.runtime.healthcheck_path, - manifest.runtime.startup_timeout_seconds, - ) - .await?; - - let result = run_framework_suite( - manifest, - &target_addr, - config.concurrency, - config.duration_secs, - config.libc, - config.target_pid, - config.cgroup_dir.as_deref(), - ) - .await?; - - persist_session_results(&result, &config.output_dir)?; - if let Some(readme) = &config.readme_path { - let _ = update_readme_leaderboard(Path::new(readme), std::slice::from_ref(&result)); + let result = async { + wait_for_ready( + &target_addr, + &manifest.runtime.healthcheck_path, + manifest.runtime.startup_timeout_seconds, + ) + .await?; + let target_pid = target + .as_ref() + .map_or(config.target_pid, |target| Some(target.pid)); + let result = run_framework_suite( + manifest, + &target_addr, + config.concurrency, + config.duration_secs, + config.libc, + target_pid, + config.cgroup_dir.as_deref(), + ) + .await?; + persist_session_results(&result, &config.output_dir)?; + if let Some(readme) = &config.readme_path { + let _ = update_readme_leaderboard(Path::new(readme), std::slice::from_ref(&result)); + } + Ok(result) } - - Ok(result) + .await; + if let Some(target) = &target { + stop_container(framework_dir, target); + } + result } fn persist_session_results(result: &RunResult, base_dir: &str) -> Result<(), OrchestratorError> { diff --git a/crates/orchestrator/src/ws_bench.rs b/crates/orchestrator/src/ws_bench.rs index f25fac8..65df809 100644 --- a/crates/orchestrator/src/ws_bench.rs +++ b/crates/orchestrator/src/ws_bench.rs @@ -14,29 +14,44 @@ pub async fn execute_websocket_workload( target_pid: Option, cgroup_dir: Option<&Path>, ) -> Result { - let sampler = BackgroundSampler::spawn_target( - target_pid, - cgroup_dir.map(Path::to_path_buf), - Duration::from_millis(50), - ); + let sampler = + BackgroundSampler::spawn_target(target_pid, cgroup_dir, Duration::from_millis(50)); + + let started = Instant::now(); + let deadline = started + Duration::from_secs(u64::from(duration_secs)); + let workers = concurrency.max(1); + let mut handles = Vec::with_capacity(workers as usize); + for _ in 0..workers { + let addr = target_addr.to_string(); + let path = endpoint_path.to_string(); + handles.push(tokio::spawn(async move { + let mut recorder = LatencyRecorder::default(); + let mut stats = WorkerStats::default(); + let _ = AutobahnSuiteRunner::run_echo_benchmark( + &addr, + &path, + deadline, + &mut recorder, + &mut stats, + ) + .await; + (recorder, stats) + })); + } - let deadline = Instant::now() + Duration::from_secs(u64::from(duration_secs)); let mut recorder = LatencyRecorder::default(); let mut stats = WorkerStats::default(); - - let _ = AutobahnSuiteRunner::run_echo_benchmark( - target_addr, - endpoint_path, - deadline, - &mut recorder, - &mut stats, - ) - .await; + for handle in handles { + if let Ok((worker_recorder, worker_stats)) = handle.await { + recorder.combine(&worker_recorder); + stats.merge(&worker_stats); + } + } let resources = sampler.stop().await; - let rps = stats.requests_completed as f64 / f64::from(duration_secs.max(1)); - let mb_per_sec = - (stats.bytes_transferred as f64 / (1024.0 * 1024.0)) / f64::from(duration_secs.max(1)); + let elapsed_secs = started.elapsed().as_secs_f64().max(0.001); + let rps = stats.requests_completed as f64 / elapsed_secs; + let mb_per_sec = (stats.bytes_transferred as f64 / (1024.0 * 1024.0)) / elapsed_secs; Ok(TestMetrics { category: "websocket".into(), diff --git a/crates/orchestrator/tests/discovery_test.rs b/crates/orchestrator/tests/discovery_test.rs index 572b7f1..efa2a9a 100644 --- a/crates/orchestrator/tests/discovery_test.rs +++ b/crates/orchestrator/tests/discovery_test.rs @@ -59,3 +59,10 @@ fn test_discover_empty_directory() { let _ = fs::remove_dir_all(temp_empty); } + +#[test] +fn test_registry_manifests_are_valid() { + let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../frameworks"); + let discovered = discover_frameworks(&root).expect("discover framework registry failed"); + assert_eq!(discovered.len(), 31); +} diff --git a/crates/orchestrator/tests/lifecycle_test.rs b/crates/orchestrator/tests/lifecycle_test.rs index e483aea..d58715e 100644 --- a/crates/orchestrator/tests/lifecycle_test.rs +++ b/crates/orchestrator/tests/lifecycle_test.rs @@ -1,6 +1,6 @@ use common::manifest::parse_manifest; use common::result::LibcVariant; -use orchestrator::lifecycle::run_framework_suite; +use orchestrator::lifecycle::{SuiteExecutionConfig, SuiteTiming, run_framework_suite_with_timing}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; @@ -63,17 +63,22 @@ startup_timeout_seconds = 2 ); let manifest = parse_manifest(&manifest_toml).unwrap(); - let result = run_framework_suite( - &manifest, - &addr.to_string(), - 4, - 1, - LibcVariant::Glibc, - None, - None, - ) - .await - .unwrap(); + let target_addr = addr.to_string(); + let config = SuiteExecutionConfig { + target_addr: &target_addr, + concurrency: 4, + duration_secs: 1, + libc: LibcVariant::Glibc, + target_pid: None, + cgroup_dir: None, + timing: SuiteTiming { + warmup_secs: 1, + cooldown_secs: 0, + }, + }; + let result = run_framework_suite_with_timing(&manifest, config) + .await + .unwrap(); assert_eq!(result.framework_name, "mock"); assert_eq!(result.benchmarks.len(), 1); diff --git a/crates/reporter/tests/readme_test.rs b/crates/reporter/tests/readme_test.rs index c79955e..f3ba46a 100644 --- a/crates/reporter/tests/readme_test.rs +++ b/crates/reporter/tests/readme_test.rs @@ -45,6 +45,7 @@ fn create_sample_run(name: &str, category: &str, rps: f64) -> RunResult { cpu_user_pct: 70.0, cpu_kernel_pct: 10.0, context_switches: 1000, + rapl_joules: None, }, status_codes: StatusCodeCounts::default(), }], diff --git a/crates/reporter/tests/reporter_test.rs b/crates/reporter/tests/reporter_test.rs index 78b0e15..a5b2fcb 100644 --- a/crates/reporter/tests/reporter_test.rs +++ b/crates/reporter/tests/reporter_test.rs @@ -48,6 +48,7 @@ fn create_dummy_run(name: &str, libc: LibcVariant, rps: f64, p99_us: u64) -> Run cpu_user_pct: 70.0, cpu_kernel_pct: 10.0, context_switches: 1000, + rapl_joules: None, }, status_codes: StatusCodeCounts::default(), }], diff --git a/deploy/autobahn/README.md b/deploy/autobahn/README.md new file mode 100644 index 0000000..7f8dc25 --- /dev/null +++ b/deploy/autobahn/README.md @@ -0,0 +1,11 @@ +# Autobahn protocol conformance + +Run a candidate WebSocket server on the benchmark host, then execute: + +```sh +scripts/run_autobahn_suite.sh +``` + +The runner uses the official pinned Autobahn image in `fuzzingclient` mode, runs every enabled upstream case, and writes its HTML and JSON report to `results/autobahn`. + +Set `AUTOBAHN_TARGET_URL` for a nondefault server endpoint, `AUTOBAHN_RESULTS_DIR` for another report directory, or `CONTAINER_RUNTIME=podman` to use Podman. diff --git a/deploy/ebpf/README.md b/deploy/ebpf/README.md new file mode 100644 index 0000000..8fd14d7 --- /dev/null +++ b/deploy/ebpf/README.md @@ -0,0 +1,11 @@ +# eBPF metrics + +Run the collector with the target container or process PID for the same interval as the benchmark measurement: + +```sh +sudo scripts/collect_ebpf.sh 1234 30 results/ebpf.txt +``` + +It emits off CPU microseconds, page cache hit and miss events, and context switches. The collector crate parses this stable text format and calculates the page cache hit ratio. + +The host needs `bpftrace`, BTF support, and permissions to attach scheduler tracepoints and the available page cache probes. diff --git a/deploy/postgres/README.md b/deploy/postgres/README.md new file mode 100644 index 0000000..8637b30 --- /dev/null +++ b/deploy/postgres/README.md @@ -0,0 +1,26 @@ +# PostgreSQL benchmark database + +This deployment supplies the TechEmpower-compatible `hello_world` database: + +- `world`: 10,000 rows with `id` and `randomnumber` +- `fortune`: 12 rows used by `/fortunes` + +Run it with Docker Compose or Podman Compose: + +```sh +docker compose -f deploy/postgres/compose.yaml up -d +podman compose -f deploy/postgres/compose.yaml up -d +``` + +The default connection string is: + +```text +postgres://benchmarkdbuser:benchmarkdbpass@127.0.0.1:5432/hello_world +``` + +Set `POSTGRES_BIND_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, +and `POSTGRES_PASSWORD` before startup to change it. PostgreSQL applies +`seed.sql` only when it initializes a new `postgres_data` volume. + +To recreate the data explicitly, use `docker compose ... down -v` or set +`BENCHMARK_PURGE_DATABASE=1` when running `scripts/cleanup_environment.sh --apply`. diff --git a/deploy/postgres/compose.yaml b/deploy/postgres/compose.yaml new file mode 100644 index 0000000..9ff1777 --- /dev/null +++ b/deploy/postgres/compose.yaml @@ -0,0 +1,24 @@ +name: framework-benchmark-postgres + +services: + postgres: + image: postgres:17.4-alpine3.21 + environment: + POSTGRES_DB: ${POSTGRES_DB:-hello_world} + POSTGRES_USER: ${POSTGRES_USER:-benchmarkdbuser} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-benchmarkdbpass} + ports: + - "${POSTGRES_BIND_ADDRESS:-127.0.0.1}:${POSTGRES_PORT:-5432}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./seed.sql:/docker-entrypoint-initdb.d/01_seed.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 2s + timeout: 2s + retries: 30 + security_opt: + - no-new-privileges:true + +volumes: + postgres_data: diff --git a/deploy/postgres/seed.sql b/deploy/postgres/seed.sql new file mode 100644 index 0000000..664e7c6 --- /dev/null +++ b/deploy/postgres/seed.sql @@ -0,0 +1,30 @@ +CREATE TABLE world ( + id INTEGER PRIMARY KEY, + randomnumber INTEGER NOT NULL +); + +INSERT INTO world (id, randomnumber) +SELECT id, ((id * 7919) % 10000) + 1 +FROM generate_series(1, 10000) AS id; + +CREATE TABLE fortune ( + id INTEGER PRIMARY KEY, + message VARCHAR(2048) NOT NULL +); + +INSERT INTO fortune (id, message) VALUES + (1, 'fortune: No such file or directory'), + (2, 'A computer scientist is someone who fixes things that aren''t broken.'), + (3, 'After enough decimal places, nobody gives a damn.'), + (4, 'A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1'), + (5, 'A computer program does what you tell it to do, not what you want it to do.'), + (6, 'Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen'), + (7, 'Any program that runs right is obsolete.'), + (8, 'A list is only as strong as its weakest link. — Donald Knuth'), + (9, 'Feature: A bug with seniority.'), + (10, 'Computers make very fast, very accurate mistakes.'), + (11, ''), + (12, 'フレームワークのベンチマーク'); + +ANALYZE world; +ANALYZE fortune; diff --git a/docs/roadmap/phase3_frameworks.md b/docs/roadmap/phase3_frameworks.md index d0bb6f6..29f2620 100644 --- a/docs/roadmap/phase3_frameworks.md +++ b/docs/roadmap/phase3_frameworks.md @@ -8,7 +8,7 @@ Phase 3 populates baseline framework implementations across major programming la ### 3.1 Framework Selection and Registry Setup - [x] Establish framework directory structure specification in `frameworks/README.md`. - [x] Implement user-selected benchmark targets across requested languages and runtimes: - - Zig: `uwebzockets`, `swerver`, `zix`, `dusty` + - Zig: `uwebzockets`, `swerver`, `dusty` - Rust: `cpeak`, `ntex`, `actix-web` - Go: `fib-tuned`, `fiber`, `fasthttp` - C# (.NET): `genhttp`, `cadente`, `aspnet-minimal` diff --git a/docs/schemas/result_schema.json b/docs/schemas/result_schema.json index f5ac078..bdcdf03 100644 --- a/docs/schemas/result_schema.json +++ b/docs/schemas/result_schema.json @@ -76,7 +76,8 @@ "avg_rss_bytes": { "type": "integer" }, "cpu_user_pct": { "type": "number" }, "cpu_kernel_pct": { "type": "number" }, - "context_switches": { "type": "integer" } + "context_switches": { "type": "integer" }, + "rapl_joules": { "type": ["number", "null"], "minimum": 0 } } }, "status_codes": { diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a5dbdda --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1790898470, + "narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "024633cd702b10285db5cb19b40ad48d2399ba60", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1791594108, + "narHash": "sha256-XK4KceonkwD8LuMAt/vNrz982mXJM5jeHAxMwsUXXAQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6510408d147d8c5e6d76043e899e9251756f6a75", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1790477367, + "narHash": "sha256-oujbxmVKDhci2E4EM3KyAbRUgp5/IsCoiA/LfEF7OOk=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "f7cd230690d9fc982d06129f9189b101fddcfa3b", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1791795448, + "narHash": "sha256-12C5ef8a3endLXBvLOVR/jynM9EHGgP1t+y5uBorwx4=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "404b7a5537f0adc6e5c558a1b94612e683dec504", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 7408a71..7e3c02b 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; rust-overlay.url = "github:oxalica/rust-overlay"; + rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = inputs @ { diff --git a/frameworks/web-server/cpp/agensio/CMakeLists.txt b/frameworks/web-server/cpp/agensio/CMakeLists.txt deleted file mode 100644 index 991529f..0000000 --- a/frameworks/web-server/cpp/agensio/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(agensio_benchmark CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -add_executable(server main.cpp) -find_package(Threads REQUIRED) -target_link_libraries(server PRIVATE Threads::Threads) diff --git a/frameworks/web-server/cpp/agensio/Dockerfile.glibc b/frameworks/web-server/cpp/agensio/Dockerfile.glibc index b8775b0..a3c6031 100644 --- a/frameworks/web-server/cpp/agensio/Dockerfile.glibc +++ b/frameworks/web-server/cpp/agensio/Dockerfile.glibc @@ -1,11 +1,21 @@ -FROM debian:bookworm-slim AS build +FROM debian:trixie AS build WORKDIR /src -RUN apt-get update -qq && apt-get install -y --no-install-recommends cmake make g++ && rm -rf /var/lib/apt/lists/* -COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + build-essential ca-certificates cmake git libasio-dev libssl-dev ninja-build \ + zlib1g-dev && rm -rf /var/lib/apt/lists/* +RUN git init /src/agensio && git -C /src/agensio remote add origin \ + https://github.com/YiannisBourkelis/agensio.git && git -C /src/agensio fetch \ + --depth 1 origin eadce75a37564fc379f33aab110408cdfa0b73a4 && \ + git -C /src/agensio checkout --detach FETCH_HEAD +RUN cmake -S /src/agensio -B /src/build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DAGENSIO_NATIVE=ON -DAGENSIO_TESTS=OFF && cmake --build /src/build --target agensio -FROM debian:bookworm-slim +FROM debian:trixie-slim WORKDIR /app -COPY --from=build /src/build/server /app/server +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + libssl3t64 zlib1g && rm -rf /var/lib/apt/lists/* +COPY agensio.toml /etc/agensio/agensio.toml +COPY www /app/www +COPY --from=build /src/build/agensio /usr/local/bin/agensio EXPOSE 8080 -ENTRYPOINT ["/app/server"] +ENTRYPOINT ["/usr/local/bin/agensio", "-c", "/etc/agensio/agensio.toml"] diff --git a/frameworks/web-server/cpp/agensio/Dockerfile.musl b/frameworks/web-server/cpp/agensio/Dockerfile.musl index abdc6a3..eef8450 100644 --- a/frameworks/web-server/cpp/agensio/Dockerfile.musl +++ b/frameworks/web-server/cpp/agensio/Dockerfile.musl @@ -1,11 +1,18 @@ FROM alpine:3.21 AS build WORKDIR /src -RUN apk add --no-cache cmake make g++ -COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN apk add --no-cache asio-dev cmake g++ git make ninja openssl-dev zlib-dev +RUN git init /src/agensio && git -C /src/agensio remote add origin \ + https://github.com/YiannisBourkelis/agensio.git && git -C /src/agensio fetch \ + --depth 1 origin eadce75a37564fc379f33aab110408cdfa0b73a4 && \ + git -C /src/agensio checkout --detach FETCH_HEAD +RUN cmake -S /src/agensio -B /src/build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DAGENSIO_NATIVE=ON -DAGENSIO_TESTS=OFF && cmake --build /src/build --target agensio FROM alpine:3.21 WORKDIR /app -COPY --from=build /src/build/server /app/server +RUN apk add --no-cache libstdc++ openssl zlib +COPY agensio.toml /etc/agensio/agensio.toml +COPY www /app/www +COPY --from=build /src/build/agensio /usr/local/bin/agensio EXPOSE 8080 -ENTRYPOINT ["/app/server"] +ENTRYPOINT ["/usr/local/bin/agensio", "-c", "/etc/agensio/agensio.toml"] diff --git a/frameworks/web-server/cpp/agensio/agensio.toml b/frameworks/web-server/cpp/agensio/agensio.toml new file mode 100644 index 0000000..0f3b43c --- /dev/null +++ b/frameworks/web-server/cpp/agensio/agensio.toml @@ -0,0 +1,32 @@ +[server] +workers = 0 +idle_timeout = 0 +protocols = ["h1"] + +[log] +access = "off" +error = "stderr" + +[[site]] +server_name = ["localhost"] +listen = ["0.0.0.0:8080"] +default = true +root = "/app/www" + +[[site.location]] +path = "/healthz" +match = "exact" +try_files = ["/healthz.txt"] +add_headers = { "Content-Type" = "text/plain; charset=utf-8" } + +[[site.location]] +path = "/plaintext" +match = "exact" +try_files = ["/plaintext.txt"] +add_headers = { "Content-Type" = "text/plain; charset=utf-8" } + +[[site.location]] +path = "/json" +match = "exact" +try_files = ["/json.json"] +add_headers = { "Content-Type" = "application/json" } diff --git a/frameworks/web-server/cpp/agensio/framework.toml b/frameworks/web-server/cpp/agensio/framework.toml index 68a4523..13c8f73 100644 --- a/frameworks/web-server/cpp/agensio/framework.toml +++ b/frameworks/web-server/cpp/agensio/framework.toml @@ -1,6 +1,6 @@ [framework] name = "agensio" -version = "0.1.0" +version = "0.1.0-alpha.72" language = "cpp" language_version = "c++20" website = "https://github.com/YiannisBourkelis/agensio" diff --git a/frameworks/web-server/cpp/agensio/main.cpp b/frameworks/web-server/cpp/agensio/main.cpp deleted file mode 100644 index 8376a23..0000000 --- a/frameworks/web-server/cpp/agensio/main.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include -#include - -int main() { - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - int opt = 1; - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - - sockaddr_in address{}; - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(8080); - - bind(server_fd, (struct sockaddr*)&address, sizeof(address)); - listen(server_fd, 4096); - - while (true) { - int client_fd = accept(server_fd, nullptr, nullptr); - if (client_fd < 0) continue; - - char buffer[1024] = {0}; - read(client_fd, buffer, sizeof(buffer) - 1); - - if (strstr(buffer, "GET /healthz") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /plaintext") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /json") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 27\r\n\r\n{\"message\": \"Hello, World!\"}"; - write(client_fd, resp, strlen(resp)); - } else { - const char* resp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } - close(client_fd); - } - return 0; -} diff --git a/frameworks/web-server/cpp/agensio/www/healthz.txt b/frameworks/web-server/cpp/agensio/www/healthz.txt new file mode 100644 index 0000000..b5754e2 --- /dev/null +++ b/frameworks/web-server/cpp/agensio/www/healthz.txt @@ -0,0 +1 @@ +ok \ No newline at end of file diff --git a/frameworks/web-server/cpp/agensio/www/json.json b/frameworks/web-server/cpp/agensio/www/json.json new file mode 100644 index 0000000..28146b6 --- /dev/null +++ b/frameworks/web-server/cpp/agensio/www/json.json @@ -0,0 +1 @@ +{"message": "Hello, World!"} \ No newline at end of file diff --git a/frameworks/web-server/cpp/agensio/www/plaintext.txt b/frameworks/web-server/cpp/agensio/www/plaintext.txt new file mode 100644 index 0000000..b45ef6f --- /dev/null +++ b/frameworks/web-server/cpp/agensio/www/plaintext.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/frameworks/web-server/cpp/drogon/CMakeLists.txt b/frameworks/web-server/cpp/drogon/CMakeLists.txt index 27241a2..b16b3fb 100644 --- a/frameworks/web-server/cpp/drogon/CMakeLists.txt +++ b/frameworks/web-server/cpp/drogon/CMakeLists.txt @@ -3,7 +3,9 @@ project(drogon_benchmark CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +find_package(Drogon CONFIG REQUIRED) add_executable(server main.cpp) -find_package(Threads REQUIRED) -target_link_libraries(server PRIVATE Threads::Threads) +target_link_libraries(server PRIVATE Drogon::Drogon) diff --git a/frameworks/web-server/cpp/drogon/Dockerfile.glibc b/frameworks/web-server/cpp/drogon/Dockerfile.glibc index b8775b0..0c18731 100644 --- a/frameworks/web-server/cpp/drogon/Dockerfile.glibc +++ b/frameworks/web-server/cpp/drogon/Dockerfile.glibc @@ -1,11 +1,22 @@ FROM debian:bookworm-slim AS build WORKDIR /src -RUN apt-get update -qq && apt-get install -y --no-install-recommends cmake make g++ && rm -rf /var/lib/apt/lists/* +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + cmake g++ git libjsoncpp-dev make uuid-dev zlib1g-dev && \ + rm -rf /var/lib/apt/lists/* +RUN git clone --depth 1 --branch v1.9.8 --recurse-submodules \ + https://github.com/drogonframework/drogon.git /opt/drogon +RUN cmake -S /opt/drogon -B /opt/drogon/build -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_BROTLI=OFF -DBUILD_CTL=OFF -DBUILD_EXAMPLES=OFF -DBUILD_ORM=OFF \ + -DBUILD_YAML_CONFIG=OFF -DCMAKE_INSTALL_PREFIX=/opt/drogon/install && \ + cmake --build /opt/drogon/build --parallel && cmake --install /opt/drogon/build COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/drogon/install && \ + cmake --build build --parallel FROM debian:bookworm-slim WORKDIR /app -COPY --from=build /src/build/server /app/server +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + libjsoncpp25 libuuid1 zlib1g && rm -rf /var/lib/apt/lists/* +COPY --from=build /src/build/server ./server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/cpp/drogon/Dockerfile.musl b/frameworks/web-server/cpp/drogon/Dockerfile.musl index abdc6a3..7e249de 100644 --- a/frameworks/web-server/cpp/drogon/Dockerfile.musl +++ b/frameworks/web-server/cpp/drogon/Dockerfile.musl @@ -1,11 +1,19 @@ FROM alpine:3.21 AS build WORKDIR /src -RUN apk add --no-cache cmake make g++ +RUN apk add --no-cache cmake g++ git jsoncpp-dev make util-linux-dev zlib-dev +RUN git clone --depth 1 --branch v1.9.8 --recurse-submodules \ + https://github.com/drogonframework/drogon.git /opt/drogon +RUN cmake -S /opt/drogon -B /opt/drogon/build -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_BROTLI=OFF -DBUILD_CTL=OFF -DBUILD_EXAMPLES=OFF -DBUILD_ORM=OFF \ + -DBUILD_YAML_CONFIG=OFF -DCMAKE_INSTALL_PREFIX=/opt/drogon/install && \ + cmake --build /opt/drogon/build --parallel && cmake --install /opt/drogon/build COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/drogon/install && \ + cmake --build build --parallel FROM alpine:3.21 WORKDIR /app -COPY --from=build /src/build/server /app/server +RUN apk add --no-cache jsoncpp libstdc++ libuuid zlib +COPY --from=build /src/build/server ./server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/cpp/drogon/main.cpp b/frameworks/web-server/cpp/drogon/main.cpp index 8376a23..b416d1d 100644 --- a/frameworks/web-server/cpp/drogon/main.cpp +++ b/frameworks/web-server/cpp/drogon/main.cpp @@ -1,44 +1,28 @@ -#include -#include -#include -#include -#include -#include +#include int main() { - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - int opt = 1; - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - - sockaddr_in address{}; - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(8080); - - bind(server_fd, (struct sockaddr*)&address, sizeof(address)); - listen(server_fd, 4096); - - while (true) { - int client_fd = accept(server_fd, nullptr, nullptr); - if (client_fd < 0) continue; - - char buffer[1024] = {0}; - read(client_fd, buffer, sizeof(buffer) - 1); - - if (strstr(buffer, "GET /healthz") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /plaintext") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /json") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 27\r\n\r\n{\"message\": \"Hello, World!\"}"; - write(client_fd, resp, strlen(resp)); - } else { - const char* resp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } - close(client_fd); - } - return 0; + drogon::app() + .registerHandler("/healthz", + [](const drogon::HttpRequestPtr &, auto &&callback) { + callback(drogon::HttpResponse::newHttpResponse()); + }) + .registerHandler("/plaintext", + [](const drogon::HttpRequestPtr &, auto &&callback) { + auto response = + drogon::HttpResponse::newHttpResponse(); + response->setContentTypeCode(drogon::CT_TEXT_PLAIN); + response->setBody("Hello, World!"); + callback(response); + }) + .registerHandler( + "/json", + [](const drogon::HttpRequestPtr &, auto &&callback) { + auto response = drogon::HttpResponse::newHttpResponse(); + response->setContentTypeCode(drogon::CT_APPLICATION_JSON); + response->setBody("{\"message\":\"Hello, World!\"}"); + callback(response); + }) + .addListener("0.0.0.0", 8080) + .setThreadNum(0) + .run(); } diff --git a/frameworks/web-server/cpp/hical/CMakeLists.txt b/frameworks/web-server/cpp/hical/CMakeLists.txt index 263fa58..ee0cd94 100644 --- a/frameworks/web-server/cpp/hical/CMakeLists.txt +++ b/frameworks/web-server/cpp/hical/CMakeLists.txt @@ -1,9 +1,10 @@ cmake_minimum_required(VERSION 3.20) -project(hical_benchmark CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +project(hical_benchmark LANGUAGES C CXX) +set(HICAL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(HICAL_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(HICAL_ENABLE_MEMORY_TRACKING OFF CACHE BOOL "" FORCE) +set(HICAL_WITH_OPENAPI OFF CACHE BOOL "" FORCE) +add_subdirectory(/opt/hical hical_build) add_executable(server main.cpp) -find_package(Threads REQUIRED) -target_link_libraries(server PRIVATE Threads::Threads) +target_link_libraries(server PRIVATE hical_core) diff --git a/frameworks/web-server/cpp/hical/Dockerfile.glibc b/frameworks/web-server/cpp/hical/Dockerfile.glibc index b8775b0..c1c72f9 100644 --- a/frameworks/web-server/cpp/hical/Dockerfile.glibc +++ b/frameworks/web-server/cpp/hical/Dockerfile.glibc @@ -1,11 +1,19 @@ -FROM debian:bookworm-slim AS build +FROM ubuntu:24.04 AS build WORKDIR /src -RUN apt-get update -qq && apt-get install -y --no-install-recommends cmake make g++ && rm -rf /var/lib/apt/lists/* +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + cmake g++ git libboost-json1.83-dev libssl-dev make zlib1g-dev && \ + rm -rf /var/lib/apt/lists/* +RUN git init /opt/hical && git -C /opt/hical remote add origin \ + https://github.com/Hical61/Hical.git && git -C /opt/hical fetch --depth 1 \ + origin e61cb09a0c699fc1ffcdb9824a8edcad88d22fb0 && \ + git -C /opt/hical checkout --detach FETCH_HEAD COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel -FROM debian:bookworm-slim +FROM ubuntu:24.04 WORKDIR /app +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + libboost-json1.83.0 libssl3 zlib1g && rm -rf /var/lib/apt/lists/* COPY --from=build /src/build/server /app/server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/cpp/hical/Dockerfile.musl b/frameworks/web-server/cpp/hical/Dockerfile.musl index abdc6a3..c16d715 100644 --- a/frameworks/web-server/cpp/hical/Dockerfile.musl +++ b/frameworks/web-server/cpp/hical/Dockerfile.musl @@ -1,11 +1,16 @@ FROM alpine:3.21 AS build WORKDIR /src -RUN apk add --no-cache cmake make g++ +RUN apk add --no-cache boost-dev cmake g++ git make openssl-dev zlib-dev +RUN git init /opt/hical && git -C /opt/hical remote add origin \ + https://github.com/Hical61/Hical.git && git -C /opt/hical fetch --depth 1 \ + origin e61cb09a0c699fc1ffcdb9824a8edcad88d22fb0 && \ + git -C /opt/hical checkout --detach FETCH_HEAD COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel FROM alpine:3.21 WORKDIR /app +RUN apk add --no-cache boost-json libstdc++ openssl zlib COPY --from=build /src/build/server /app/server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/cpp/hical/framework.toml b/frameworks/web-server/cpp/hical/framework.toml index 338596f..d653ab7 100644 --- a/frameworks/web-server/cpp/hical/framework.toml +++ b/frameworks/web-server/cpp/hical/framework.toml @@ -1,6 +1,6 @@ [framework] name = "hical" -version = "0.1.0" +version = "2.7.0" language = "cpp" language_version = "c++20" website = "https://github.com/Hical61/Hical" diff --git a/frameworks/web-server/cpp/hical/main.cpp b/frameworks/web-server/cpp/hical/main.cpp index 8376a23..b987f33 100644 --- a/frameworks/web-server/cpp/hical/main.cpp +++ b/frameworks/web-server/cpp/hical/main.cpp @@ -1,44 +1,21 @@ -#include -#include -#include -#include -#include -#include +#include "core/HttpServer.h" -int main() { - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - int opt = 1; - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - - sockaddr_in address{}; - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(8080); +using namespace hical; - bind(server_fd, (struct sockaddr*)&address, sizeof(address)); - listen(server_fd, 4096); - - while (true) { - int client_fd = accept(server_fd, nullptr, nullptr); - if (client_fd < 0) continue; +int main() { + HttpServer server(8080); + server.setIdleTimeout(0); - char buffer[1024] = {0}; - read(client_fd, buffer, sizeof(buffer) - 1); + server.router().get("/healthz", [](const HttpRequest &) -> HttpResponse { + return HttpResponse::ok(); + }); + server.router().get("/plaintext", [](const HttpRequest &) -> HttpResponse { + return HttpResponse::ok("Hello, World!", "text/plain; charset=utf-8"); + }); + server.router().get("/json", [](const HttpRequest &) -> HttpResponse { + return HttpResponse::ok("{\"message\": \"Hello, World!\"}", + "application/json"); + }); - if (strstr(buffer, "GET /healthz") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /plaintext") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /json") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 27\r\n\r\n{\"message\": \"Hello, World!\"}"; - write(client_fd, resp, strlen(resp)); - } else { - const char* resp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } - close(client_fd); - } - return 0; + server.start(); } diff --git a/frameworks/web-server/cpp/uwebsockets/CMakeLists.txt b/frameworks/web-server/cpp/uwebsockets/CMakeLists.txt index 17057e3..cb15878 100644 --- a/frameworks/web-server/cpp/uwebsockets/CMakeLists.txt +++ b/frameworks/web-server/cpp/uwebsockets/CMakeLists.txt @@ -3,7 +3,16 @@ project(uwebsockets_benchmark CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) -add_executable(server main.cpp) find_package(Threads REQUIRED) -target_link_libraries(server PRIVATE Threads::Threads) + +add_library(usockets STATIC IMPORTED) +set_target_properties(usockets PROPERTIES + IMPORTED_LOCATION /opt/uwebsockets/uSockets/uSockets.a + INTERFACE_INCLUDE_DIRECTORIES /opt/uwebsockets/uSockets/src) + +add_executable(server main.cpp) +target_include_directories(server PRIVATE /opt/uwebsockets/src) +target_compile_definitions(server PRIVATE LIBUS_NO_SSL UWS_NO_ZLIB) +target_link_libraries(server PRIVATE usockets Threads::Threads) diff --git a/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc b/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc index b8775b0..a7d2850 100644 --- a/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc +++ b/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc @@ -1,11 +1,17 @@ FROM debian:bookworm-slim AS build WORKDIR /src -RUN apt-get update -qq && apt-get install -y --no-install-recommends cmake make g++ && rm -rf /var/lib/apt/lists/* +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + cmake g++ git make && rm -rf /var/lib/apt/lists/* +RUN git clone --depth 1 --branch v20.69.0 --recurse-submodules \ + https://github.com/uNetworking/uWebSockets.git /opt/uwebsockets && \ + make -C /opt/uwebsockets/uSockets COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel FROM debian:bookworm-slim WORKDIR /app -COPY --from=build /src/build/server /app/server +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + libstdc++6 && rm -rf /var/lib/apt/lists/* +COPY --from=build /src/build/server ./server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl b/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl index abdc6a3..dcfe532 100644 --- a/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl +++ b/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl @@ -1,11 +1,15 @@ FROM alpine:3.21 AS build WORKDIR /src -RUN apk add --no-cache cmake make g++ +RUN apk add --no-cache cmake g++ git make +RUN git clone --depth 1 --branch v20.69.0 --recurse-submodules \ + https://github.com/uNetworking/uWebSockets.git /opt/uwebsockets && \ + make -C /opt/uwebsockets/uSockets COPY CMakeLists.txt main.cpp ./ -RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel FROM alpine:3.21 WORKDIR /app -COPY --from=build /src/build/server /app/server +RUN apk add --no-cache libstdc++ +COPY --from=build /src/build/server ./server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/cpp/uwebsockets/main.cpp b/frameworks/web-server/cpp/uwebsockets/main.cpp index 8376a23..bca4c87 100644 --- a/frameworks/web-server/cpp/uwebsockets/main.cpp +++ b/frameworks/web-server/cpp/uwebsockets/main.cpp @@ -1,44 +1,39 @@ -#include -#include -#include -#include -#include -#include +#include -int main() { - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - int opt = 1; - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - - sockaddr_in address{}; - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(8080); +#include +#include +#include +#include - bind(server_fd, (struct sockaddr*)&address, sizeof(address)); - listen(server_fd, 4096); +static void run_server() { + uWS::App() + .get("/healthz", [](auto *response, auto *) { response->end(); }) + .get("/plaintext", + [](auto *response, auto *) { + response->writeHeader("Content-Type", "text/plain; charset=utf-8") + ->end("Hello, World!"); + }) + .get("/json", + [](auto *response, auto *) { + response->writeHeader("Content-Type", "application/json") + ->end("{\"message\": \"Hello, World!\"}"); + }) + .listen(8080, + [](auto *listen_socket) { + if (!listen_socket) + std::exit(EXIT_FAILURE); + }) + .run(); +} - while (true) { - int client_fd = accept(server_fd, nullptr, nullptr); - if (client_fd < 0) continue; +int main() { + const auto thread_count = std::max(1u, std::thread::hardware_concurrency()); + std::vector workers; - char buffer[1024] = {0}; - read(client_fd, buffer, sizeof(buffer) - 1); + workers.reserve(thread_count); + for (unsigned int index = 0; index < thread_count; ++index) + workers.emplace_back(run_server); - if (strstr(buffer, "GET /healthz") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /plaintext") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"; - write(client_fd, resp, strlen(resp)); - } else if (strstr(buffer, "GET /json") == buffer) { - const char* resp = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 27\r\n\r\n{\"message\": \"Hello, World!\"}"; - write(client_fd, resp, strlen(resp)); - } else { - const char* resp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; - write(client_fd, resp, strlen(resp)); - } - close(client_fd); - } - return 0; + for (auto &worker : workers) + worker.join(); } diff --git a/frameworks/web-server/csharp/aspnet-minimal/Program.cs b/frameworks/web-server/csharp/aspnet-minimal/Program.cs index 37a5b27..2613edf 100644 --- a/frameworks/web-server/csharp/aspnet-minimal/Program.cs +++ b/frameworks/web-server/csharp/aspnet-minimal/Program.cs @@ -1,8 +1,9 @@ var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); +const string Json = "{\"message\":\"Hello, World!\"}"; app.MapGet("/healthz", () => Results.Ok()); app.MapGet("/plaintext", () => Results.Text("Hello, World!", "text/plain; charset=utf-8")); -app.MapGet("/json", () => Results.Json(new { message = "Hello, World!" })); +app.MapGet("/json", () => Results.Text(Json, "application/json")); app.Run("http://0.0.0.0:8080"); diff --git a/frameworks/web-server/csharp/cadente/Program.cs b/frameworks/web-server/csharp/cadente/Program.cs index 37a5b27..2260924 100644 --- a/frameworks/web-server/csharp/cadente/Program.cs +++ b/frameworks/web-server/csharp/cadente/Program.cs @@ -1,8 +1,22 @@ -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); +using System.Text; +using Sisk.Cadente.CoreEngine; +using Sisk.Core.Http; -app.MapGet("/healthz", () => Results.Ok()); -app.MapGet("/plaintext", () => Results.Text("Hello, World!", "text/plain; charset=utf-8")); -app.MapGet("/json", () => Results.Json(new { message = "Hello, World!" })); +using var app = HttpServer.CreateBuilder() + .UseEngine(new CadenteHttpServerEngine()) + .UseListeningPort("http://0.0.0.0:8080/") + .UseMinimalConfiguration() + .UseRouter(router => + { + router.MapGet("/healthz", _ => new HttpResponse()); + router.MapGet("/plaintext", _ => Response("Hello, World!", "text/plain; charset=utf-8")); + router.MapGet("/json", _ => Response("{\"message\":\"Hello, World!\"}", "application/json")); + }) + .Build(); -app.Run("http://0.0.0.0:8080"); +app.Start(verbose: false); + +static HttpResponse Response(string body, string content_type) => new() +{ + Content = new StringContent(body, Encoding.UTF8, content_type), +}; diff --git a/frameworks/web-server/csharp/cadente/cadente.csproj b/frameworks/web-server/csharp/cadente/cadente.csproj index 0c27b6c..4a4802a 100644 --- a/frameworks/web-server/csharp/cadente/cadente.csproj +++ b/frameworks/web-server/csharp/cadente/cadente.csproj @@ -1,7 +1,12 @@ - + + Exe net9.0 enable enable + + + + diff --git a/frameworks/web-server/csharp/cadente/framework.toml b/frameworks/web-server/csharp/cadente/framework.toml index 58e04c8..f0cc0e8 100644 --- a/frameworks/web-server/csharp/cadente/framework.toml +++ b/frameworks/web-server/csharp/cadente/framework.toml @@ -1,6 +1,6 @@ [framework] name = "cadente" -version = "1.0.0" +version = "1.6.2-beta12" language = "csharp" language_version = ".net 9" website = "https://github.com/sisk-http/core" diff --git a/frameworks/web-server/go/fasthttp/go.mod b/frameworks/web-server/go/fasthttp/go.mod index 6ab4772..8a0bb35 100644 --- a/frameworks/web-server/go/fasthttp/go.mod +++ b/frameworks/web-server/go/fasthttp/go.mod @@ -2,6 +2,10 @@ module fasthttp-benchmark go 1.23 +require github.com/valyala/fasthttp v1.58.0 + require ( - github.com/valyala/fasthttp v1.58.0 + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect ) diff --git a/frameworks/web-server/go/fasthttp/go.sum b/frameworks/web-server/go/fasthttp/go.sum new file mode 100644 index 0000000..8179105 --- /dev/null +++ b/frameworks/web-server/go/fasthttp/go.sum @@ -0,0 +1,10 @@ +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= +github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= diff --git a/frameworks/web-server/go/fasthttp/main.go b/frameworks/web-server/go/fasthttp/main.go index 68ce198..c2ea37f 100644 --- a/frameworks/web-server/go/fasthttp/main.go +++ b/frameworks/web-server/go/fasthttp/main.go @@ -15,10 +15,10 @@ func requestHandler(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) case "/plaintext": ctx.SetContentType("text/plain; charset=utf-8") - ctx.SetBody(plaintextBody) + ctx.Response.SetBodyRaw(plaintextBody) case "/json": ctx.SetContentType("application/json") - ctx.SetBody(jsonBody) + ctx.Response.SetBodyRaw(jsonBody) default: ctx.Error("Not Found", fasthttp.StatusNotFound) } diff --git a/frameworks/web-server/go/fib-tuned/go.mod b/frameworks/web-server/go/fib-tuned/go.mod index b56c14b..81de87d 100644 --- a/frameworks/web-server/go/fib-tuned/go.mod +++ b/frameworks/web-server/go/fib-tuned/go.mod @@ -2,6 +2,18 @@ module fibtuned-benchmark go 1.23 +require github.com/gofiber/fiber/v2 v2.52.6 + require ( - github.com/gofiber/fiber/v2 v2.52.6 + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/frameworks/web-server/go/fib-tuned/go.sum b/frameworks/web-server/go/fib-tuned/go.sum new file mode 100644 index 0000000..edf0334 --- /dev/null +++ b/frameworks/web-server/go/fib-tuned/go.sum @@ -0,0 +1,27 @@ +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/frameworks/web-server/go/fib-tuned/main.go b/frameworks/web-server/go/fib-tuned/main.go index b0f5c3d..4388511 100644 --- a/frameworks/web-server/go/fib-tuned/main.go +++ b/frameworks/web-server/go/fib-tuned/main.go @@ -16,7 +16,7 @@ func main() { }) app.Get("/healthz", func(c *fiber.Ctx) error { - return c.SendStatus(fiber.StatusOK) + return c.Send(nil) }) app.Get("/plaintext", func(c *fiber.Ctx) error { diff --git a/frameworks/web-server/go/fiber/go.mod b/frameworks/web-server/go/fiber/go.mod index 1a17546..447ce08 100644 --- a/frameworks/web-server/go/fiber/go.mod +++ b/frameworks/web-server/go/fiber/go.mod @@ -2,6 +2,18 @@ module fiber-benchmark go 1.23 +require github.com/gofiber/fiber/v2 v2.52.6 + require ( - github.com/gofiber/fiber/v2 v2.52.6 + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/frameworks/web-server/go/fiber/go.sum b/frameworks/web-server/go/fiber/go.sum new file mode 100644 index 0000000..edf0334 --- /dev/null +++ b/frameworks/web-server/go/fiber/go.sum @@ -0,0 +1,27 @@ +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/frameworks/web-server/go/fiber/main.go b/frameworks/web-server/go/fiber/main.go index 37fb434..3e5cd37 100644 --- a/frameworks/web-server/go/fiber/main.go +++ b/frameworks/web-server/go/fiber/main.go @@ -1,12 +1,11 @@ package main -import ( - "github.com/gofiber/fiber/v2" -) +import "github.com/gofiber/fiber/v2" -type JsonMessage struct { - Message string `json:"message"` -} +var ( + plaintextBody = []byte("Hello, World!") + jsonBody = []byte("{\"message\": \"Hello, World!\"}") +) func main() { app := fiber.New(fiber.Config{ @@ -14,18 +13,17 @@ func main() { }) app.Get("/healthz", func(c *fiber.Ctx) error { - return c.SendStatus(fiber.StatusOK) + return c.Send(nil) }) app.Get("/plaintext", func(c *fiber.Ctx) error { c.Set("Content-Type", "text/plain; charset=utf-8") - return c.SendString("Hello, World!") + return c.Send(plaintextBody) }) app.Get("/json", func(c *fiber.Ctx) error { - return c.JSON(JsonMessage{ - Message: "Hello, World!", - }) + c.Set("Content-Type", "application/json") + return c.Send(jsonBody) }) _ = app.Listen(":8080") diff --git a/frameworks/web-server/java/rapidoid/Dockerfile.glibc b/frameworks/web-server/java/rapidoid/Dockerfile.glibc index 0b7a9c2..506f301 100644 --- a/frameworks/web-server/java/rapidoid/Dockerfile.glibc +++ b/frameworks/web-server/java/rapidoid/Dockerfile.glibc @@ -6,6 +6,6 @@ RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre-jammy WORKDIR /app -COPY --from=build /src/target/*.jar /app/app.jar +COPY --from=build /src/target/rapidoid-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/frameworks/web-server/java/rapidoid/Dockerfile.musl b/frameworks/web-server/java/rapidoid/Dockerfile.musl index aec8c5b..90e650b 100644 --- a/frameworks/web-server/java/rapidoid/Dockerfile.musl +++ b/frameworks/web-server/java/rapidoid/Dockerfile.musl @@ -6,6 +6,6 @@ RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre-alpine WORKDIR /app -COPY --from=build /src/target/*.jar /app/app.jar +COPY --from=build /src/target/rapidoid-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/frameworks/web-server/java/rapidoid/Main.java b/frameworks/web-server/java/rapidoid/Main.java index 8f9d032..1fe81ec 100644 --- a/frameworks/web-server/java/rapidoid/Main.java +++ b/frameworks/web-server/java/rapidoid/Main.java @@ -1,12 +1,16 @@ +import org.rapidoid.http.MediaType; +import org.rapidoid.http.Req; import org.rapidoid.setup.On; -import java.util.Collections; +import java.nio.charset.StandardCharsets; public class Main { + private static final byte[] JSON = "{\"message\":\"Hello, World!\"}".getBytes(StandardCharsets.UTF_8); + public static void main(String[] args) { On.port(8080); On.get("/healthz").plain(() -> ""); On.get("/plaintext").plain(() -> "Hello, World!"); - On.get("/json").json(() -> Collections.singletonMap("message", "Hello, World!")); + On.get("/json").html((Req req) -> req.response().contentType(MediaType.JSON).body(JSON)); } } diff --git a/frameworks/web-server/java/rapidoid/pom.xml b/frameworks/web-server/java/rapidoid/pom.xml index 498243f..e1edf65 100644 --- a/frameworks/web-server/java/rapidoid/pom.xml +++ b/frameworks/web-server/java/rapidoid/pom.xml @@ -17,4 +17,14 @@ 5.5.5 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + packageshadefalseMain + + + diff --git a/frameworks/web-server/java/undertow/Dockerfile.glibc b/frameworks/web-server/java/undertow/Dockerfile.glibc index 0b7a9c2..f5f0f8b 100644 --- a/frameworks/web-server/java/undertow/Dockerfile.glibc +++ b/frameworks/web-server/java/undertow/Dockerfile.glibc @@ -6,6 +6,6 @@ RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre-jammy WORKDIR /app -COPY --from=build /src/target/*.jar /app/app.jar +COPY --from=build /src/target/undertow-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/frameworks/web-server/java/undertow/Dockerfile.musl b/frameworks/web-server/java/undertow/Dockerfile.musl index aec8c5b..48ac83f 100644 --- a/frameworks/web-server/java/undertow/Dockerfile.musl +++ b/frameworks/web-server/java/undertow/Dockerfile.musl @@ -6,6 +6,6 @@ RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre-alpine WORKDIR /app -COPY --from=build /src/target/*.jar /app/app.jar +COPY --from=build /src/target/undertow-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/frameworks/web-server/java/undertow/pom.xml b/frameworks/web-server/java/undertow/pom.xml index 9bc7158..3c5f000 100644 --- a/frameworks/web-server/java/undertow/pom.xml +++ b/frameworks/web-server/java/undertow/pom.xml @@ -17,4 +17,14 @@ 2.3.18.Final + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + packageshadefalseMain + + + diff --git a/frameworks/web-server/java/vertx/Dockerfile.glibc b/frameworks/web-server/java/vertx/Dockerfile.glibc index 0b7a9c2..b54a556 100644 --- a/frameworks/web-server/java/vertx/Dockerfile.glibc +++ b/frameworks/web-server/java/vertx/Dockerfile.glibc @@ -6,6 +6,6 @@ RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre-jammy WORKDIR /app -COPY --from=build /src/target/*.jar /app/app.jar +COPY --from=build /src/target/vertx-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/frameworks/web-server/java/vertx/Dockerfile.musl b/frameworks/web-server/java/vertx/Dockerfile.musl index aec8c5b..bd2df7c 100644 --- a/frameworks/web-server/java/vertx/Dockerfile.musl +++ b/frameworks/web-server/java/vertx/Dockerfile.musl @@ -6,6 +6,6 @@ RUN mvn clean package -DskipTests FROM eclipse-temurin:21-jre-alpine WORKDIR /app -COPY --from=build /src/target/*.jar /app/app.jar +COPY --from=build /src/target/vertx-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/frameworks/web-server/java/vertx/Main.java b/frameworks/web-server/java/vertx/Main.java index 0e979cd..3b8dc62 100644 --- a/frameworks/web-server/java/vertx/Main.java +++ b/frameworks/web-server/java/vertx/Main.java @@ -1,9 +1,12 @@ import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpServer; -import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; public class Main { + private static final Buffer PLAINTEXT = Buffer.buffer("Hello, World!"); + private static final Buffer JSON = Buffer.buffer("{\"message\":\"Hello, World!\"}"); + public static void main(String[] args) { Vertx vertx = Vertx.vertx(); HttpServer server = vertx.createHttpServer(); @@ -13,11 +16,11 @@ public static void main(String[] args) { router.get("/plaintext").handler(ctx -> ctx.response() .putHeader("Content-Type", "text/plain; charset=utf-8") - .end("Hello, World!")); + .end(PLAINTEXT)); router.get("/json").handler(ctx -> ctx.response() .putHeader("Content-Type", "application/json") - .end(new JsonObject().put("message", "Hello, World!").encode())); + .end(JSON)); server.requestHandler(router).listen(8080); } diff --git a/frameworks/web-server/java/vertx/pom.xml b/frameworks/web-server/java/vertx/pom.xml index 9a4a3d4..d4531c0 100644 --- a/frameworks/web-server/java/vertx/pom.xml +++ b/frameworks/web-server/java/vertx/pom.xml @@ -17,4 +17,14 @@ 4.5.10 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + packageshadefalseMain + + + diff --git a/frameworks/web-server/javascript/bun/Dockerfile.glibc b/frameworks/web-server/javascript/bun/Dockerfile.glibc index bf56ebc..f649ed8 100644 --- a/frameworks/web-server/javascript/bun/Dockerfile.glibc +++ b/frameworks/web-server/javascript/bun/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM oven/bun:1.2-slim +FROM oven/bun:1.3.3-slim WORKDIR /app COPY package.json server.ts ./ EXPOSE 8080 diff --git a/frameworks/web-server/javascript/bun/Dockerfile.musl b/frameworks/web-server/javascript/bun/Dockerfile.musl index 10be4f3..74cbdf6 100644 --- a/frameworks/web-server/javascript/bun/Dockerfile.musl +++ b/frameworks/web-server/javascript/bun/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM oven/bun:1.2-alpine +FROM oven/bun:1.3.3-alpine WORKDIR /app COPY package.json server.ts ./ EXPOSE 8080 diff --git a/frameworks/web-server/javascript/bun/framework.toml b/frameworks/web-server/javascript/bun/framework.toml index 870ab67..83be134 100644 --- a/frameworks/web-server/javascript/bun/framework.toml +++ b/frameworks/web-server/javascript/bun/framework.toml @@ -1,8 +1,8 @@ [framework] name = "bun" -version = "1.2.2" +version = "1.3.3" language = "javascript" -language_version = "bun-1.2" +language_version = "bun-1.3" website = "https://bun.sh/" category = "web-server" tier = "micro" diff --git a/frameworks/web-server/javascript/bun/server.ts b/frameworks/web-server/javascript/bun/server.ts index 5b22404..3055876 100644 --- a/frameworks/web-server/javascript/bun/server.ts +++ b/frameworks/web-server/javascript/bun/server.ts @@ -1,18 +1,13 @@ -const server = Bun.serve({ +Bun.serve({ port: 8080, - fetch(req) { - const url = new URL(req.url); - if (url.pathname === "/healthz") { - return new Response(null, { status: 200 }); - } - if (url.pathname === "/plaintext") { - return new Response("Hello, World!", { - headers: { "Content-Type": "text/plain; charset=utf-8" }, - }); - } - if (url.pathname === "/json") { - return Response.json({ message: "Hello, World!" }); - } - return new Response("Not Found", { status: 404 }); + routes: { + "/healthz": new Response(null, { status: 200 }), + "/plaintext": new Response("Hello, World!", { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + }), + "/json": new Response('{"message":"Hello, World!"}', { + headers: { "Content-Type": "application/json" }, + }), }, + fetch: () => new Response(null, { status: 404 }), }); diff --git a/frameworks/web-server/javascript/elysia/Dockerfile.glibc b/frameworks/web-server/javascript/elysia/Dockerfile.glibc index 684b2e2..54ab07f 100644 --- a/frameworks/web-server/javascript/elysia/Dockerfile.glibc +++ b/frameworks/web-server/javascript/elysia/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM oven/bun:1.2-slim +FROM oven/bun:1.3.3-slim WORKDIR /app COPY package.json ./ RUN bun install --production diff --git a/frameworks/web-server/javascript/elysia/Dockerfile.musl b/frameworks/web-server/javascript/elysia/Dockerfile.musl index cc068d0..8f34647 100644 --- a/frameworks/web-server/javascript/elysia/Dockerfile.musl +++ b/frameworks/web-server/javascript/elysia/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM oven/bun:1.2-alpine +FROM oven/bun:1.3.3-alpine WORKDIR /app COPY package.json ./ RUN bun install --production diff --git a/frameworks/web-server/javascript/elysia/framework.toml b/frameworks/web-server/javascript/elysia/framework.toml index b6cd276..83f78b3 100644 --- a/frameworks/web-server/javascript/elysia/framework.toml +++ b/frameworks/web-server/javascript/elysia/framework.toml @@ -1,8 +1,8 @@ [framework] name = "elysia" -version = "1.2.0" +version = "1.3.3" language = "javascript" -language_version = "bun-1.2" +language_version = "bun-1.3" website = "https://elysiajs.com/" category = "web-server" tier = "micro" diff --git a/frameworks/web-server/javascript/elysia/package.json b/frameworks/web-server/javascript/elysia/package.json index c0dbae0..e205550 100644 --- a/frameworks/web-server/javascript/elysia/package.json +++ b/frameworks/web-server/javascript/elysia/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "elysia": "^1.2.0" + "elysia": "1.3.3" }, "scripts": { "start": "bun run server.ts" diff --git a/frameworks/web-server/javascript/elysia/server.ts b/frameworks/web-server/javascript/elysia/server.ts index 5ad961e..fec7456 100644 --- a/frameworks/web-server/javascript/elysia/server.ts +++ b/frameworks/web-server/javascript/elysia/server.ts @@ -1,9 +1,7 @@ import { Elysia } from "elysia"; -new Elysia() - .get("/healthz", () => new Response(null, { status: 200 })) - .get("/plaintext", () => new Response("Hello, World!", { - headers: { "Content-Type": "text/plain; charset=utf-8" }, - })) - .get("/json", () => ({ message: "Hello, World!" })) +new Elysia({ aot: true, nativeStaticResponse: true }) + .get("/healthz", "") + .get("/plaintext", "Hello, World!") + .get("/json", { message: "Hello, World!" }) .listen(8080); diff --git a/frameworks/web-server/javascript/fulmine/Dockerfile.glibc b/frameworks/web-server/javascript/fulmine/Dockerfile.glibc index 33ebd19..de1adc6 100644 --- a/frameworks/web-server/javascript/fulmine/Dockerfile.glibc +++ b/frameworks/web-server/javascript/fulmine/Dockerfile.glibc @@ -1,5 +1,9 @@ -FROM node:22-bookworm-slim +FROM node:22-trixie-slim WORKDIR /app -COPY package.json server.js ./ +RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates git && rm -rf /var/lib/apt/lists/* +RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/ +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev --ignore-scripts +COPY server.js ./ EXPOSE 8080 ENTRYPOINT ["node", "server.js"] diff --git a/frameworks/web-server/javascript/fulmine/Dockerfile.musl b/frameworks/web-server/javascript/fulmine/Dockerfile.musl index 6734df7..6c30239 100644 --- a/frameworks/web-server/javascript/fulmine/Dockerfile.musl +++ b/frameworks/web-server/javascript/fulmine/Dockerfile.musl @@ -1,5 +1,9 @@ FROM node:22-alpine3.21 WORKDIR /app -COPY package.json server.js ./ +RUN apk add --no-cache git +RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/ +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev --ignore-scripts +COPY server.js ./ EXPOSE 8080 ENTRYPOINT ["node", "server.js"] diff --git a/frameworks/web-server/javascript/fulmine/framework.toml b/frameworks/web-server/javascript/fulmine/framework.toml index f585d46..9c49bf0 100644 --- a/frameworks/web-server/javascript/fulmine/framework.toml +++ b/frameworks/web-server/javascript/fulmine/framework.toml @@ -1,6 +1,6 @@ [framework] name = "fulmine" -version = "1.0.0" +version = "5.21.4" language = "javascript" language_version = "node-22" website = "https://github.com/nigrosimone/fulmine.js" diff --git a/frameworks/web-server/javascript/fulmine/package-lock.json b/frameworks/web-server/javascript/fulmine/package-lock.json new file mode 100644 index 0000000..c704d51 --- /dev/null +++ b/frameworks/web-server/javascript/fulmine/package-lock.json @@ -0,0 +1,720 @@ +{ + "name": "fulmine-benchmark", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fulmine-benchmark", + "version": "0.1.0", + "dependencies": { + "express": "npm:fulmine.js@5.21.4" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/express": { + "name": "fulmine.js", + "version": "5.21.4", + "resolved": "https://registry.npmjs.org/fulmine.js/-/fulmine.js-5.21.4.tgz", + "integrity": "sha512-/luZyiKxjK/W60/K6IKx46zcH+V2BPMmXcAlVtYoe6p6lpV9kk1Qo8szmB4T9AgRKjpibMPLNU/jwa26QTZ7vw==", + "license": "Apache-2.0", + "dependencies": { + "@types/express": "^5.0.6", + "accepts": "^2.0.0", + "acorn": "^8.18.0", + "bytes": "^3.1.2", + "compressible": "^2.0.18", + "content-disposition": "^1.1.0", + "cookie": "^0.7.2", + "cookie-signature": "^1.2.2", + "encodeurl": "^2.0.0", + "fast-decode-uri-component": "^1.0.1", + "fast-zlib": "^2.0.1", + "fresh": "^2.0.0", + "iconv-lite": "^0.7.3", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "proxy-addr": "^2.0.7", + "qs": "^6.15.3", + "range-parser": "^1.3.0", + "statuses": "^2.0.2", + "tseep": "^1.3.1", + "type-is": "^2.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.71.0", + "vary": "^1.1.2" + }, + "bin": { + "fulmine": "src/cli.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@nestjs/platform-express": ">=10" + }, + "peerDependenciesMeta": { + "@nestjs/platform-express": { + "optional": true + } + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-zlib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-zlib/-/fast-zlib-2.0.1.tgz", + "integrity": "sha512-DCoYgNagM2Bt1VIpXpdGnRx4LzqJeYG0oh6Nf/7cWo6elTXkFGMw9CrRCYYUIapYNrozYMoyDRflx9mgT3Awyw==", + "license": "MIT", + "funding": { + "type": "patreon", + "url": "https://patreon.com/timotejroiko" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/tseep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.3.1.tgz", + "integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==", + "license": "MIT" + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/uWebSockets.js": { + "version": "20.71.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#9ec36de7709996c58dce03d63796216c90a17136", + "license": "Apache-2.0" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/frameworks/web-server/javascript/fulmine/package.json b/frameworks/web-server/javascript/fulmine/package.json index 544449b..a980e7f 100644 --- a/frameworks/web-server/javascript/fulmine/package.json +++ b/frameworks/web-server/javascript/fulmine/package.json @@ -3,6 +3,9 @@ "version": "0.1.0", "private": true, "type": "module", + "dependencies": { + "express": "npm:fulmine.js@5.21.4" + }, "scripts": { "start": "node server.js" } diff --git a/frameworks/web-server/javascript/fulmine/server.js b/frameworks/web-server/javascript/fulmine/server.js index 0adaefb..c6d25aa 100644 --- a/frameworks/web-server/javascript/fulmine/server.js +++ b/frameworks/web-server/javascript/fulmine/server.js @@ -1,28 +1,13 @@ -import http from "node:http"; +import express from "express"; -const plaintext = Buffer.from("Hello, World!"); -const json = Buffer.from(JSON.stringify({ message: "Hello, World!" })); +const app = express({ cluster: "auto" }); -const server = http.createServer((req, res) => { - if (req.url === "/healthz") { - res.writeHead(200); - res.end(); - } else if (req.url === "/plaintext") { - res.writeHead(200, { - "Content-Type": "text/plain; charset=utf-8", - "Content-Length": plaintext.length, - }); - res.end(plaintext); - } else if (req.url === "/json") { - res.writeHead(200, { - "Content-Type": "application/json", - "Content-Length": json.length, - }); - res.end(json); - } else { - res.writeHead(404); - res.end(); - } -}); +app.get("/healthz", (_, response) => response.status(200).end()); +app.get("/plaintext", (_, response) => + response.type("text/plain").send("Hello, World!"), +); +app.get("/json", (_, response) => + response.type("application/json").send('{"message":"Hello, World!"}'), +); -server.listen(8080); +app.listen(8080); diff --git a/frameworks/web-server/php/frankenphp/Dockerfile.glibc b/frameworks/web-server/php/frankenphp/Dockerfile.glibc index 789035a..659e3f8 100644 --- a/frameworks/web-server/php/frankenphp/Dockerfile.glibc +++ b/frameworks/web-server/php/frankenphp/Dockerfile.glibc @@ -1,6 +1,6 @@ FROM dunglas/frankenphp:latest-bookworm WORKDIR /app -COPY server.php /app/index.php +COPY server.php /app/worker.php EXPOSE 8080 ENV SERVER_NAME=":8080" -ENTRYPOINT ["frankenphp", "php-server", "--listen", ":8080", "--root", "/app"] +ENTRYPOINT ["frankenphp", "php-server", "--listen", ":8080", "--root", "/app", "--worker", "/app/worker.php"] diff --git a/frameworks/web-server/php/frankenphp/Dockerfile.musl b/frameworks/web-server/php/frankenphp/Dockerfile.musl index f776efa..fa996a2 100644 --- a/frameworks/web-server/php/frankenphp/Dockerfile.musl +++ b/frameworks/web-server/php/frankenphp/Dockerfile.musl @@ -1,6 +1,6 @@ FROM dunglas/frankenphp:latest-alpine WORKDIR /app -COPY server.php /app/index.php +COPY server.php /app/worker.php EXPOSE 8080 ENV SERVER_NAME=":8080" -ENTRYPOINT ["frankenphp", "php-server", "--listen", ":8080", "--root", "/app"] +ENTRYPOINT ["frankenphp", "php-server", "--listen", ":8080", "--root", "/app", "--worker", "/app/worker.php"] diff --git a/frameworks/web-server/php/frankenphp/server.php b/frameworks/web-server/php/frankenphp/server.php index 48fd950..53feed9 100644 --- a/frameworks/web-server/php/frankenphp/server.php +++ b/frameworks/web-server/php/frankenphp/server.php @@ -1,22 +1,30 @@ "Hello, World!"]); - exit; -} + if ($uri === '/plaintext') { + header('Content-Type: text/plain; charset=utf-8'); + echo PLAINTEXT; + return; + } -http_response_code(404); + if ($uri === '/json') { + header('Content-Type: application/json'); + echo JSON; + return; + } + + http_response_code(404); +}; + +while (frankenphp_handle_request($handler)) { +} diff --git a/frameworks/web-server/php/swoole/server.php b/frameworks/web-server/php/swoole/server.php index adfb7bc..4b07b6d 100644 --- a/frameworks/web-server/php/swoole/server.php +++ b/frameworks/web-server/php/swoole/server.php @@ -4,9 +4,20 @@ use Swoole\Http\Request; use Swoole\Http\Response; -$http = new Server("0.0.0.0", 8080); - -$http->on("request", function (Request $request, Response $response) { +const PLAINTEXT = "Hello, World!"; +const JSON = '{"message":"Hello, World!"}'; + +$workers = filter_var(getenv('BENCHMARK_WORKERS'), FILTER_VALIDATE_INT, [ + 'options' => ['min_range' => 1], +]) ?: swoole_cpu_num(); +$http = new Server('0.0.0.0', 8080); +$http->set([ + 'worker_num' => $workers, + 'open_tcp_nodelay' => true, + 'log_level' => SWOOLE_LOG_WARNING, +]); + +$http->on('request', function (Request $request, Response $response): void { $uri = $request->server['request_uri']; if ($uri === '/healthz') { @@ -16,14 +27,14 @@ } if ($uri === '/plaintext') { - $response->header("Content-Type", "text/plain; charset=utf-8"); - $response->end("Hello, World!"); + $response->header('Content-Type', 'text/plain; charset=utf-8'); + $response->end(PLAINTEXT); return; } if ($uri === '/json') { - $response->header("Content-Type", "application/json"); - $response->end(json_encode(["message" => "Hello, World!"])); + $response->header('Content-Type', 'application/json'); + $response->end(JSON); return; } diff --git a/frameworks/web-server/php/workerman/server.php b/frameworks/web-server/php/workerman/server.php index 7e84d08..577c35d 100644 --- a/frameworks/web-server/php/workerman/server.php +++ b/frameworks/web-server/php/workerman/server.php @@ -5,8 +5,12 @@ use Workerman\Worker; use Workerman\Protocols\Http\Response; +const PLAINTEXT = "Hello, World!"; +const JSON = '{"message":"Hello, World!"}'; + $http_worker = new Worker("http://0.0.0.0:8080"); -$http_worker->count = 4; +$http_worker->count = worker_count(); +$http_worker->reusePort = true; $http_worker->onMessage = function ($connection, $request) { $path = $request->path(); @@ -20,7 +24,7 @@ $connection->send(new Response( 200, ['Content-Type' => 'text/plain; charset=utf-8'], - 'Hello, World!' + PLAINTEXT )); return; } @@ -29,7 +33,7 @@ $connection->send(new Response( 200, ['Content-Type' => 'application/json'], - json_encode(['message' => 'Hello, World!']) + JSON )); return; } @@ -38,3 +42,11 @@ }; Worker::runAll(); + +function worker_count(): int +{ + $configured = filter_var(getenv('BENCHMARK_WORKERS'), FILTER_VALIDATE_INT, [ + 'options' => ['min_range' => 1], + ]); + return $configured ?: 1; +} diff --git a/frameworks/web-server/python/blacksheep/Dockerfile.glibc b/frameworks/web-server/python/blacksheep/Dockerfile.glibc index d2b2c24..e2b8a98 100644 --- a/frameworks/web-server/python/blacksheep/Dockerfile.glibc +++ b/frameworks/web-server/python/blacksheep/Dockerfile.glibc @@ -4,4 +4,4 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ EXPOSE 8080 -ENTRYPOINT ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"] +ENTRYPOINT ["sh", "-c", "exec uvicorn app:app --host 0.0.0.0 --port 8080 --loop uvloop --http httptools --no-access-log --workers ${BENCHMARK_WORKERS:-1}"] diff --git a/frameworks/web-server/python/blacksheep/Dockerfile.musl b/frameworks/web-server/python/blacksheep/Dockerfile.musl index e7b03fe..1e0c5b0 100644 --- a/frameworks/web-server/python/blacksheep/Dockerfile.musl +++ b/frameworks/web-server/python/blacksheep/Dockerfile.musl @@ -5,4 +5,4 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ EXPOSE 8080 -ENTRYPOINT ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"] +ENTRYPOINT ["sh", "-c", "exec uvicorn app:app --host 0.0.0.0 --port 8080 --loop uvloop --http httptools --no-access-log --workers ${BENCHMARK_WORKERS:-1}"] diff --git a/frameworks/web-server/python/blacksheep/app.py b/frameworks/web-server/python/blacksheep/app.py index 7664277..10f3a20 100644 --- a/frameworks/web-server/python/blacksheep/app.py +++ b/frameworks/web-server/python/blacksheep/app.py @@ -1,15 +1,20 @@ -from blacksheep import Application, json, text +from blacksheep import Application, Content, Response app = Application() +PLAINTEXT = Content(b"text/plain", b"Hello, World!") +JSON = Content(b"application/json", b'{"message":"Hello, World!"}') + @app.route("/healthz") -async def healthz(): - return text("") +async def healthz() -> Response: + return Response(200) + @app.route("/plaintext") -async def plaintext(): - return text("Hello, World!") +async def plaintext() -> Response: + return Response(200, content=PLAINTEXT) + @app.route("/json") -async def get_json(): - return json({"message": "Hello, World!"}) +async def get_json() -> Response: + return Response(200, content=JSON) diff --git a/frameworks/web-server/python/blacksheep/requirements.txt b/frameworks/web-server/python/blacksheep/requirements.txt index f179a03..38ea18e 100644 --- a/frameworks/web-server/python/blacksheep/requirements.txt +++ b/frameworks/web-server/python/blacksheep/requirements.txt @@ -1,2 +1,4 @@ -blacksheep>=2.0.7 -uvicorn>=0.34.0 +blacksheep==2.0.7 +httptools==0.6.4 +uvicorn==0.34.0 +uvloop==0.21.0 diff --git a/frameworks/web-server/python/granian/Dockerfile.glibc b/frameworks/web-server/python/granian/Dockerfile.glibc index 32b9bc7..4e2819f 100644 --- a/frameworks/web-server/python/granian/Dockerfile.glibc +++ b/frameworks/web-server/python/granian/Dockerfile.glibc @@ -4,4 +4,4 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ EXPOSE 8080 -ENTRYPOINT ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "8080", "app:app"] +ENTRYPOINT ["sh", "-c", "exec granian --interface asgi --host 0.0.0.0 --port 8080 --loop uvloop --no-access-log --workers ${BENCHMARK_WORKERS:-1} app:app"] diff --git a/frameworks/web-server/python/granian/Dockerfile.musl b/frameworks/web-server/python/granian/Dockerfile.musl index 2be9deb..e43e295 100644 --- a/frameworks/web-server/python/granian/Dockerfile.musl +++ b/frameworks/web-server/python/granian/Dockerfile.musl @@ -5,4 +5,4 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ EXPOSE 8080 -ENTRYPOINT ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "8080", "app:app"] +ENTRYPOINT ["sh", "-c", "exec granian --interface asgi --host 0.0.0.0 --port 8080 --loop uvloop --no-access-log --workers ${BENCHMARK_WORKERS:-1} app:app"] diff --git a/frameworks/web-server/python/granian/app.py b/frameworks/web-server/python/granian/app.py index ebc27ab..571fd2a 100644 --- a/frameworks/web-server/python/granian/app.py +++ b/frameworks/web-server/python/granian/app.py @@ -2,6 +2,16 @@ PLAINTEXT_RESP = b"Hello, World!" JSON_RESP = json.dumps({"message": "Hello, World!"}).encode("utf-8") +EMPTY_HEADERS = ((b"content-length", b"0"),) +PLAINTEXT_HEADERS = ( + (b"content-type", b"text/plain; charset=utf-8"), + (b"content-length", b"13"), +) +JSON_HEADERS = ( + (b"content-type", b"application/json"), + (b"content-length", str(len(JSON_RESP)).encode("ascii")), +) + async def app(scope, receive, send): if scope["type"] != "http": @@ -9,36 +19,38 @@ async def app(scope, receive, send): path = scope["path"] if path == "/healthz": - await send({ - "type": "http.response.start", - "status": 200, - "headers": [[b"content-length", b"0"]], - }) + await send( + { + "type": "http.response.start", + "status": 200, + "headers": EMPTY_HEADERS, + } + ) await send({"type": "http.response.body", "body": b""}) elif path == "/plaintext": - await send({ - "type": "http.response.start", - "status": 200, - "headers": [ - [b"content-type", b"text/plain; charset=utf-8"], - [b"content-length", str(len(PLAINTEXT_RESP)).encode("ascii")], - ], - }) + await send( + { + "type": "http.response.start", + "status": 200, + "headers": PLAINTEXT_HEADERS, + } + ) await send({"type": "http.response.body", "body": PLAINTEXT_RESP}) elif path == "/json": - await send({ - "type": "http.response.start", - "status": 200, - "headers": [ - [b"content-type", b"application/json"], - [b"content-length", str(len(JSON_RESP)).encode("ascii")], - ], - }) + await send( + { + "type": "http.response.start", + "status": 200, + "headers": JSON_HEADERS, + } + ) await send({"type": "http.response.body", "body": JSON_RESP}) else: - await send({ - "type": "http.response.start", - "status": 404, - "headers": [[b"content-length", b"0"]], - }) + await send( + { + "type": "http.response.start", + "status": 404, + "headers": EMPTY_HEADERS, + } + ) await send({"type": "http.response.body", "body": b""}) diff --git a/frameworks/web-server/python/granian/requirements.txt b/frameworks/web-server/python/granian/requirements.txt index 0234135..8539329 100644 --- a/frameworks/web-server/python/granian/requirements.txt +++ b/frameworks/web-server/python/granian/requirements.txt @@ -1 +1,2 @@ -granian>=1.6.0 +granian==1.7.5 +uvloop==0.21.0 diff --git a/frameworks/web-server/python/robyn/Dockerfile.glibc b/frameworks/web-server/python/robyn/Dockerfile.glibc index 3d93566..cec0fcb 100644 --- a/frameworks/web-server/python/robyn/Dockerfile.glibc +++ b/frameworks/web-server/python/robyn/Dockerfile.glibc @@ -4,4 +4,4 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ EXPOSE 8080 -ENTRYPOINT ["python", "app.py"] +ENTRYPOINT ["python", "app.py", "--fast"] diff --git a/frameworks/web-server/python/robyn/Dockerfile.musl b/frameworks/web-server/python/robyn/Dockerfile.musl index f5531e4..5632426 100644 --- a/frameworks/web-server/python/robyn/Dockerfile.musl +++ b/frameworks/web-server/python/robyn/Dockerfile.musl @@ -5,4 +5,4 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ EXPOSE 8080 -ENTRYPOINT ["python", "app.py"] +ENTRYPOINT ["python", "app.py", "--fast"] diff --git a/frameworks/web-server/python/robyn/app.py b/frameworks/web-server/python/robyn/app.py index 0a474ea..a912a3d 100644 --- a/frameworks/web-server/python/robyn/app.py +++ b/frameworks/web-server/python/robyn/app.py @@ -1,18 +1,28 @@ -from robyn import Robyn, jsonify +from robyn import Response, Robyn app = Robyn(__file__) +PLAINTEXT = "Hello, World!" +JSON = '{"message":"Hello, World!"}' + @app.get("/healthz") async def healthz(): return "" + @app.get("/plaintext") async def plaintext(): - return "Hello, World!" + return PLAINTEXT + @app.get("/json") async def get_json(): - return jsonify({"message": "Hello, World!"}) + return Response( + status_code=200, + headers={"Content-Type": "application/json"}, + body=JSON, + ) + if __name__ == "__main__": app.start(port=8080, host="0.0.0.0") diff --git a/frameworks/web-server/python/robyn/requirements.txt b/frameworks/web-server/python/robyn/requirements.txt index e9b108a..ab6273c 100644 --- a/frameworks/web-server/python/robyn/requirements.txt +++ b/frameworks/web-server/python/robyn/requirements.txt @@ -1 +1 @@ -robyn>=0.60.0 +robyn==0.62.0 diff --git a/frameworks/web-server/ruby/agoo/Dockerfile.glibc b/frameworks/web-server/ruby/agoo/Dockerfile.glibc index f5fbea0..6135db8 100644 --- a/frameworks/web-server/ruby/agoo/Dockerfile.glibc +++ b/frameworks/web-server/ruby/agoo/Dockerfile.glibc @@ -1,5 +1,6 @@ FROM ruby:3.3-bookworm WORKDIR /app +ENV RUBY_YJIT_ENABLE=1 RUN apt-get update -qq && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/* COPY Gemfile ./ RUN bundle install diff --git a/frameworks/web-server/ruby/agoo/Dockerfile.musl b/frameworks/web-server/ruby/agoo/Dockerfile.musl index ef613ef..171d268 100644 --- a/frameworks/web-server/ruby/agoo/Dockerfile.musl +++ b/frameworks/web-server/ruby/agoo/Dockerfile.musl @@ -1,5 +1,6 @@ FROM ruby:3.3-alpine3.21 WORKDIR /app +ENV RUBY_YJIT_ENABLE=1 RUN apk add --no-cache build-base COPY Gemfile ./ RUN bundle install diff --git a/frameworks/web-server/ruby/agoo/server.rb b/frameworks/web-server/ruby/agoo/server.rb index d5c079d..3e10b7e 100644 --- a/frameworks/web-server/ruby/agoo/server.rb +++ b/frameworks/web-server/ruby/agoo/server.rb @@ -1,23 +1,13 @@ require 'agoo' -require 'json' +require 'etc' -class Handler - def call(env) - path = env['PATH_INFO'] - if path == '/healthz' - [200, {}, []] - elsif path == '/plaintext' - [200, { 'Content-Type' => 'text/plain; charset=utf-8' }, ['Hello, World!']] - elsif path == '/json' - [200, { 'Content-Type' => 'application/json' }, [JSON.generate({ message: 'Hello, World!' })]] - else - [404, {}, []] - end - end -end +HEALTH = [200, {}, []].freeze +PLAINTEXT = [200, { 'Content-Type' => 'text/plain; charset=utf-8' }, ['Hello, World!']].freeze +JSON = [200, { 'Content-Type' => 'application/json' }, ['{"message":"Hello, World!"}']].freeze +THREADS = Integer(ENV.fetch('BENCHMARK_THREADS', Etc.nprocessors.to_s), exception: false) || 1 -Agoo::Server.init(8080, '.', thread_count: 1) -Agoo::Server.handle(:GET, '/healthz', Handler.new) -Agoo::Server.handle(:GET, '/plaintext', Handler.new) -Agoo::Server.handle(:GET, '/json', Handler.new) +Agoo::Server.init(8080, '.', thread_count: [THREADS, 1].max) +Agoo::Server.handle(:GET, '/healthz', ->(_) { HEALTH }) +Agoo::Server.handle(:GET, '/plaintext', ->(_) { PLAINTEXT }) +Agoo::Server.handle(:GET, '/json', ->(_) { JSON }) Agoo::Server.start diff --git a/frameworks/web-server/ruby/falcon/Dockerfile.glibc b/frameworks/web-server/ruby/falcon/Dockerfile.glibc index 5f91d70..b4121e9 100644 --- a/frameworks/web-server/ruby/falcon/Dockerfile.glibc +++ b/frameworks/web-server/ruby/falcon/Dockerfile.glibc @@ -5,4 +5,5 @@ COPY Gemfile ./ RUN bundle install COPY config.ru ./ EXPOSE 8080 -ENTRYPOINT ["falcon", "serve", "--bind", "http://0.0.0.0:8080"] +ENV RUBY_YJIT_ENABLE=1 +ENTRYPOINT ["sh", "-c", "exec falcon serve --bind http://0.0.0.0:8080 --count ${BENCHMARK_WORKERS:-1}"] diff --git a/frameworks/web-server/ruby/falcon/Dockerfile.musl b/frameworks/web-server/ruby/falcon/Dockerfile.musl index 7c1e8dc..fd15b80 100644 --- a/frameworks/web-server/ruby/falcon/Dockerfile.musl +++ b/frameworks/web-server/ruby/falcon/Dockerfile.musl @@ -5,4 +5,5 @@ COPY Gemfile ./ RUN bundle install COPY config.ru ./ EXPOSE 8080 -ENTRYPOINT ["falcon", "serve", "--bind", "http://0.0.0.0:8080"] +ENV RUBY_YJIT_ENABLE=1 +ENTRYPOINT ["sh", "-c", "exec falcon serve --bind http://0.0.0.0:8080 --count ${BENCHMARK_WORKERS:-1}"] diff --git a/frameworks/web-server/ruby/falcon/config.ru b/frameworks/web-server/ruby/falcon/config.ru index b25ea0e..01c9f81 100644 --- a/frameworks/web-server/ruby/falcon/config.ru +++ b/frameworks/web-server/ruby/falcon/config.ru @@ -1,13 +1,15 @@ -require 'json' +HEALTH = [200, {}, []].freeze +PLAINTEXT = [200, { 'content-type' => 'text/plain; charset=utf-8' }, ['Hello, World!']].freeze +JSON = [200, { 'content-type' => 'application/json' }, ['{"message":"Hello, World!"}']].freeze run lambda { |env| path = env['PATH_INFO'] if path == '/healthz' - [200, {}, []] + HEALTH elsif path == '/plaintext' - [200, { 'content-type' => 'text/plain; charset=utf-8' }, ['Hello, World!']] + PLAINTEXT elsif path == '/json' - [200, { 'content-type' => 'application/json' }, [JSON.generate({ message: 'Hello, World!' })]] + JSON else [404, {}, []] end diff --git a/frameworks/web-server/ruby/iodine/Dockerfile.glibc b/frameworks/web-server/ruby/iodine/Dockerfile.glibc index f5fbea0..6135db8 100644 --- a/frameworks/web-server/ruby/iodine/Dockerfile.glibc +++ b/frameworks/web-server/ruby/iodine/Dockerfile.glibc @@ -1,5 +1,6 @@ FROM ruby:3.3-bookworm WORKDIR /app +ENV RUBY_YJIT_ENABLE=1 RUN apt-get update -qq && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/* COPY Gemfile ./ RUN bundle install diff --git a/frameworks/web-server/ruby/iodine/Dockerfile.musl b/frameworks/web-server/ruby/iodine/Dockerfile.musl index ef613ef..171d268 100644 --- a/frameworks/web-server/ruby/iodine/Dockerfile.musl +++ b/frameworks/web-server/ruby/iodine/Dockerfile.musl @@ -1,5 +1,6 @@ FROM ruby:3.3-alpine3.21 WORKDIR /app +ENV RUBY_YJIT_ENABLE=1 RUN apk add --no-cache build-base COPY Gemfile ./ RUN bundle install diff --git a/frameworks/web-server/ruby/iodine/server.rb b/frameworks/web-server/ruby/iodine/server.rb index 8d7b7ad..3389f49 100644 --- a/frameworks/web-server/ruby/iodine/server.rb +++ b/frameworks/web-server/ruby/iodine/server.rb @@ -1,20 +1,20 @@ require 'iodine' -require 'json' +HEALTH = [200, {}, []].freeze +PLAINTEXT = [200, { 'content-type' => 'text/plain; charset=utf-8' }, ['Hello, World!']].freeze +JSON = [200, { 'content-type' => 'application/json' }, ['{"message":"Hello, World!"}']].freeze app = proc do |env| path = env['PATH_INFO'] if path == '/healthz' - [200, {}, []] + HEALTH elsif path == '/plaintext' - [200, { 'content-type' => 'text/plain; charset=utf-8' }, ['Hello, World!']] + PLAINTEXT elsif path == '/json' - [200, { 'content-type' => 'application/json' }, [JSON.generate({ message: 'Hello, World!' })]] + JSON else [404, {}, []] end end Iodine.listen(service: :http, port: 8080, handler: app) -Iodine.threads = 1 -Iodine.workers = 1 Iodine.start diff --git a/frameworks/web-server/rust/actix-web/Cargo.lock b/frameworks/web-server/rust/actix-web/Cargo.lock new file mode 100644 index 0000000..5bfd216 --- /dev/null +++ b/frameworks/web-server/rust/actix-web/Cargo.lock @@ -0,0 +1,1841 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-benchmark" +version = "0.1.0" +dependencies = [ + "actix-web", + "deadpool-postgres", + "serde", + "serde_json", + "tokio-postgres", +] + +[[package]] +name = "actix-codec" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c13df95297bcf9014dc89162b0cc69431e192e34e3b419612fc124cfcd45dbf" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.18.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f290edc569ad10c07287eebb96629686b37f1e55283a093569151d3adde0c7" +dependencies = [ + "actix-codec", + "actix-service", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f794807f82bbd36430c12cd600c73bbab0f52fdde4f0ed49978df113f4807f" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c8e46e9b40172e4cc67adcd172bd42e932a5a0aa7f603eee88e60f70452f22" +dependencies = [ + "actix-rt", + "actix-service", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824c7250d7e468feb7903e41efc5d6883cb1f3957e8db2c3d2402a9ff430b886" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96b09c4878563f8ab4a5fd0c59f9f0d6e0e9f60eb9b748526a0b9604fd89c50" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bytestring" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +dependencies = [ + "cmov", +] + +[[package]] +name = "deadpool" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e" +dependencies = [ + "deadpool-runtime", + "tokio", +] + +[[package]] +name = "deadpool-postgres" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a536565624b97fc19f758cd01b15d12908d3344425066efc8162236fbd3749" +dependencies = [ + "async-trait", + "deadpool", + "getrandom", + "tokio", + "tokio-postgres", + "tracing", +] + +[[package]] +name = "deadpool-runtime" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae" +dependencies = [ + "tokio", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3a73c82a0b0747dba739b380c046a140b5ae747234bf701df3460282da7193" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "postgres-protocol" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" +dependencies = [ + "libc", + "libredox", + "objc2-system-configuration", + "wasite", + "web-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.1+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeec9eaf2dffbbd09201e23bd0ffcbaa33bb8e9266a10734fd7ed90a85eca078" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/frameworks/web-server/rust/actix-web/Cargo.toml b/frameworks/web-server/rust/actix-web/Cargo.toml index ee4bb2c..a373605 100644 --- a/frameworks/web-server/rust/actix-web/Cargo.toml +++ b/frameworks/web-server/rust/actix-web/Cargo.toml @@ -5,5 +5,9 @@ edition = "2021" [dependencies] actix-web = "4.9" +deadpool-postgres = "0.14" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +tokio-postgres = "0.7" + +[workspace] diff --git a/frameworks/web-server/rust/actix-web/Dockerfile.glibc b/frameworks/web-server/rust/actix-web/Dockerfile.glibc index b321cb1..179f144 100644 --- a/frameworks/web-server/rust/actix-web/Dockerfile.glibc +++ b/frameworks/web-server/rust/actix-web/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM rust:1.85-bookworm AS builder +FROM rust:1.88-bookworm AS builder WORKDIR /usr/src/app COPY . . RUN cargo build --release diff --git a/frameworks/web-server/rust/actix-web/Dockerfile.musl b/frameworks/web-server/rust/actix-web/Dockerfile.musl index ec1939c..adb7c57 100644 --- a/frameworks/web-server/rust/actix-web/Dockerfile.musl +++ b/frameworks/web-server/rust/actix-web/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM rust:1.85-alpine3.21 AS builder +FROM rust:1.88-alpine3.21 AS builder WORKDIR /usr/src/app RUN apk add --no-cache musl-dev COPY . . diff --git a/frameworks/web-server/rust/actix-web/framework.toml b/frameworks/web-server/rust/actix-web/framework.toml index efacaf0..fa90380 100644 --- a/frameworks/web-server/rust/actix-web/framework.toml +++ b/frameworks/web-server/rust/actix-web/framework.toml @@ -2,7 +2,7 @@ name = "actix-web" version = "4.9.0" language = "rust" -language_version = "1.85" +language_version = "1.88" website = "https://actix.rs/" category = "web-server" tier = "micro" @@ -10,10 +10,10 @@ tier = "micro" [capabilities] plaintext = true json = true -db_single = false -db_multi = false -fortunes = false -updates = false +db_single = true +db_multi = true +fortunes = true +updates = true websocket = false http2 = false http3 = false diff --git a/frameworks/web-server/rust/actix-web/src/database.rs b/frameworks/web-server/rust/actix-web/src/database.rs new file mode 100644 index 0000000..2743a3d --- /dev/null +++ b/frameworks/web-server/rust/actix-web/src/database.rs @@ -0,0 +1,192 @@ +use actix_web::{error::ErrorInternalServerError, web, HttpResponse, Result}; +use deadpool_postgres::Pool; +use serde::{Deserialize, Serialize}; +use std::sync::atomic::{AtomicU64, Ordering}; + +pub struct AppState { + pub pool: Pool, + pub sequence: AtomicU64, +} + +#[derive(Deserialize)] +pub struct QueryCount { + #[serde(default = "default_query_count")] + pub queries: u16, +} + +#[derive(Serialize)] +struct World { + id: i32, + randomnumber: i32, +} + +struct Fortune { + id: i32, + message: String, +} + +pub async fn db_single(state: web::Data) -> Result { + let mut client = state.pool.get().await.map_err(ErrorInternalServerError)?; + let world = fetch_world(&mut client, next_id(&state)).await?; + Ok(HttpResponse::Ok().json(world)) +} + +pub async fn db_multi( + state: web::Data, + count: web::Query, +) -> Result { + let mut client = state.pool.get().await.map_err(ErrorInternalServerError)?; + let count = normalized_count(count.queries); + let mut worlds = Vec::with_capacity(count); + for _ in 0..count { + worlds.push(fetch_world(&mut client, next_id(&state)).await?); + } + Ok(HttpResponse::Ok().json(worlds)) +} + +pub async fn fortunes(state: web::Data) -> Result { + let client = state.pool.get().await.map_err(ErrorInternalServerError)?; + let rows = client + .query("SELECT id, message FROM fortune", &[]) + .await + .map_err(ErrorInternalServerError)?; + let mut fortunes: Vec<_> = rows + .iter() + .map(|row| Fortune { + id: row.get(0), + message: row.get(1), + }) + .collect(); + fortunes.push(Fortune { + id: 0, + message: "Additional fortune added at request time.".into(), + }); + fortunes.sort_unstable_by(|left, right| left.message.cmp(&right.message)); + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(render_fortunes(&fortunes))) +} + +pub async fn updates( + state: web::Data, + count: web::Query, +) -> Result { + let mut client = state.pool.get().await.map_err(ErrorInternalServerError)?; + let transaction = client + .transaction() + .await + .map_err(ErrorInternalServerError)?; + let count = normalized_count(count.queries); + let mut worlds = Vec::with_capacity(count); + let mut ids = Vec::with_capacity(count); + let mut randomnumbers = Vec::with_capacity(count); + for _ in 0..count { + let id = next_id(&state); + let row = transaction + .query_one("SELECT id, randomnumber FROM world WHERE id = $1", &[&id]) + .await + .map_err(ErrorInternalServerError)?; + let randomnumber = next_id(&state); + ids.push(id); + randomnumbers.push(randomnumber); + worlds.push(World { + id: row.get(0), + randomnumber, + }); + } + transaction + .execute( + "UPDATE world SET randomnumber = changes.randomnumber \ + FROM unnest($1::int[], $2::int[]) AS changes(id, randomnumber) \ + WHERE world.id = changes.id", + &[&ids, &randomnumbers], + ) + .await + .map_err(ErrorInternalServerError)?; + transaction + .commit() + .await + .map_err(ErrorInternalServerError)?; + Ok(HttpResponse::Ok().json(worlds)) +} + +async fn fetch_world(client: &mut deadpool_postgres::Client, id: i32) -> Result { + let row = client + .query_one("SELECT id, randomnumber FROM world WHERE id = $1", &[&id]) + .await + .map_err(ErrorInternalServerError)?; + Ok(World { + id: row.get(0), + randomnumber: row.get(1), + }) +} + +fn normalized_count(count: u16) -> usize { + usize::from(count.clamp(1, 500)) +} + +fn default_query_count() -> u16 { + 1 +} + +fn next_id(state: &AppState) -> i32 { + let mut current = state.sequence.load(Ordering::Relaxed); + loop { + let next = current ^ (current << 13) ^ (current >> 7) ^ (current << 17); + match state.sequence.compare_exchange_weak( + current, + next, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => return (next % 10_000 + 1) as i32, + Err(value) => current = value, + } + } +} + +fn render_fortunes(fortunes: &[Fortune]) -> String { + let mut html = + String::from(""); + for fortune in fortunes { + html.push_str(""); + } + html.push_str("
idmessage
"); + html.push_str(&fortune.id.to_string()); + html.push_str(""); + escape_html(&fortune.message, &mut html); + html.push_str("
"); + html +} + +fn escape_html(value: &str, output: &mut String) { + for character in value.chars() { + match character { + '&' => output.push_str("&"), + '<' => output.push_str("<"), + '>' => output.push_str(">"), + '"' => output.push_str("""), + '\'' => output.push_str("'"), + _ => output.push(character), + } + } +} + +#[cfg(test)] +mod tests { + use super::{escape_html, normalized_count}; + + #[test] + fn normalizes_query_count() { + assert_eq!(normalized_count(0), 1); + assert_eq!(normalized_count(20), 20); + assert_eq!(normalized_count(501), 500); + } + + #[test] + fn escapes_fortune_html() { + let mut output = String::new(); + escape_html("<&>\"'", &mut output); + assert_eq!(output, "<&>"'"); + } +} diff --git a/frameworks/web-server/rust/actix-web/src/main.rs b/frameworks/web-server/rust/actix-web/src/main.rs index e718851..ea10834 100644 --- a/frameworks/web-server/rust/actix-web/src/main.rs +++ b/frameworks/web-server/rust/actix-web/src/main.rs @@ -1,10 +1,14 @@ -use actix_web::{App, HttpResponse, HttpServer, Responder, get, web}; -use serde::Serialize; +mod database; -#[derive(Serialize)] -struct Message { - message: &'static str, -} +use actix_web::{get, web, App, HttpResponse, HttpServer, Responder}; +use database::AppState; +use deadpool_postgres::{Manager, ManagerConfig, Pool, RecyclingMethod}; +use std::env; +use std::sync::atomic::AtomicU64; +use tokio_postgres::NoTls; + +const PLAINTEXT: &[u8] = b"Hello, World!"; +const JSON: &[u8] = b"{\"message\":\"Hello, World!\"}"; #[get("/healthz")] async fn healthz() -> impl Responder { @@ -15,20 +19,71 @@ async fn healthz() -> impl Responder { async fn plaintext() -> impl Responder { HttpResponse::Ok() .content_type("text/plain; charset=utf-8") - .body("Hello, World!") + .body(web::Bytes::from_static(PLAINTEXT)) } #[get("/json")] async fn json() -> impl Responder { - web::Json(Message { - message: "Hello, World!", - }) + HttpResponse::Ok() + .content_type("application/json") + .body(web::Bytes::from_static(JSON)) } #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(healthz).service(plaintext).service(json)) - .bind(("0.0.0.0", 8080))? - .run() - .await + let pool = create_pool()?; + let state = web::Data::new(AppState { + pool, + sequence: AtomicU64::new(0x9e37_79b9_7f4a_7c15), + }); + let workers = configured_workers(); + HttpServer::new(move || { + App::new() + .app_data(state.clone()) + .service(healthz) + .service(plaintext) + .service(json) + .route("/db", web::get().to(database::db_single)) + .route("/queries", web::get().to(database::db_multi)) + .route("/fortunes", web::get().to(database::fortunes)) + .route("/updates", web::get().to(database::updates)) + }) + .workers(workers) + .bind(("0.0.0.0", 8080))? + .run() + .await +} + +fn create_pool() -> std::io::Result { + let url = env::var("DATABASE_URL").unwrap_or_else(|_| { + "postgres://benchmarkdbuser:benchmarkdbpass@127.0.0.1:5432/hello_world".into() + }); + let config = url.parse().map_err(std::io::Error::other)?; + let manager = Manager::from_config( + config, + NoTls, + ManagerConfig { + recycling_method: RecyclingMethod::Fast, + }, + ); + Pool::builder(manager) + .max_size(configured_pool_size()) + .build() + .map_err(std::io::Error::other) +} + +fn configured_workers() -> usize { + env::var("BENCHMARK_WORKERS") + .ok() + .and_then(|value| value.parse().ok()) + .filter(|value: &usize| *value > 0) + .unwrap_or_else(|| std::thread::available_parallelism().map_or(1, usize::from)) +} + +fn configured_pool_size() -> usize { + env::var("BENCHMARK_DB_POOL_SIZE") + .ok() + .and_then(|value| value.parse().ok()) + .filter(|value: &usize| *value > 0) + .unwrap_or_else(|| configured_workers().saturating_mul(4).clamp(4, 128)) } diff --git a/frameworks/web-server/rust/cpeak/Cargo.lock b/frameworks/web-server/rust/cpeak/Cargo.lock new file mode 100644 index 0000000..79ffc7b --- /dev/null +++ b/frameworks/web-server/rust/cpeak/Cargo.lock @@ -0,0 +1,1092 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cpeak" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e381fe1453a365572966b1a68c0d0999450999e4b9969687af830e00b839b21" +dependencies = [ + "brotli", + "bytes", + "flate2", + "getrandom 0.3.4", + "hmac", + "httpdate", + "hyper", + "hyper-util", + "libc", + "libdeflater", + "memchr", + "mimalloc", + "pbkdf2", + "regex", + "rustls", + "serde", + "serde_json", + "sha2", + "socket2", + "sonic-rs", + "tokio", +] + +[[package]] +name = "cpeak-benchmark" +version = "0.1.0" +dependencies = [ + "cpeak", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "faststr" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca7d44d22004409a61c393afb3369c8f7bb74abcae49fe249ee01dcc3002113" +dependencies = [ + "bytes", + "rkyv", + "serde", + "simdutf8", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libdeflate-sys" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7870e5fbd2766179a937c725fb11f4ca0ef025d982beb61bd3ce755425bd19c" +dependencies = [ + "cc", +] + +[[package]] +name = "libdeflater" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cbb8285d2e2bd4b8d4155840ae81929354b4ba0d7b3c272c84761ffa2141e1f" +dependencies = [ + "libdeflate-sys", +] + +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rancor" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399" +dependencies = [ + "bytes", + "hashbrown", + "indexmap", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sonic-number" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c350a05f295f922fc153a3af8b443c10db70904a9291a624ddb6058c6110d0" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "sonic-rs" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756586caeeb21126a57c17d1ef2b9386e2613ba784aae0f27104f5e9982335ec" +dependencies = [ + "ahash", + "bumpalo", + "bytes", + "cfg-if", + "faststr", + "itoa", + "ref-cast", + "serde", + "simdutf8", + "sonic-number", + "sonic-simd", + "thiserror", + "zmij", +] + +[[package]] +name = "sonic-simd" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f99e664ecd2d85a68c87e3c7a3cfe691f647ea9e835de984aba4d54a41f817d4" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/frameworks/web-server/rust/cpeak/Cargo.toml b/frameworks/web-server/rust/cpeak/Cargo.toml index ab50048..b555549 100644 --- a/frameworks/web-server/rust/cpeak/Cargo.toml +++ b/frameworks/web-server/rust/cpeak/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -tokio = { version = "1.43", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +cpeak = "0.3.0" + +[workspace] diff --git a/frameworks/web-server/rust/cpeak/src/main.rs b/frameworks/web-server/rust/cpeak/src/main.rs index 3665a94..381e55a 100644 --- a/frameworks/web-server/rust/cpeak/src/main.rs +++ b/frameworks/web-server/rust/cpeak/src/main.rs @@ -1,48 +1,27 @@ -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::{TcpListener, TcpStream}; +use cpeak::{Cpeak, CpeakError, CpeakOptions, CpeakRequest, CpeakResponse}; -#[tokio::main] -async fn main() -> Result<(), Box> { - let listener = TcpListener::bind("0.0.0.0:8080").await?; - loop { - let (socket, _) = listener.accept().await?; - tokio::spawn(async move { - let _ = handle_connection(socket).await; - }); - } +fn main() { + let mut server = Cpeak::new(CpeakOptions::default()); + server.route("GET", "/healthz", healthz); + server.route("GET", "/plaintext", plaintext); + server.route("GET", "/json", json); + server.listen("0.0.0.0:8080", || {}).wait(); } -async fn handle_connection(mut socket: TcpStream) -> Result<(), Box> { - let mut buf = [0u8; 1024]; - loop { - let n = socket.read(&mut buf).await?; - if n == 0 { - break; - } - let req = std::str::from_utf8(&buf[..n])?; - if req.starts_with("GET /healthz") { - socket - .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") - .await?; - } else if req.starts_with("GET /plaintext") { - let body = b"Hello, World!"; - let res = format!( - "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: {}\r\n\r\nHello, World!", - body.len() - ); - socket.write_all(res.as_bytes()).await?; - } else if req.starts_with("GET /json") { - let body = b"{\"message\": \"Hello, World!\"}"; - let res = format!( - "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{\"message\": \"Hello, World!\"}", - body.len() - ); - socket.write_all(res.as_bytes()).await?; - } else { - socket - .write_all(b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n") - .await?; - } - } - Ok(()) +async fn healthz(_: &mut CpeakRequest, response: &mut CpeakResponse) -> Result<(), CpeakError> { + response.status(200).end("") +} + +async fn plaintext(_: &mut CpeakRequest, response: &mut CpeakResponse) -> Result<(), CpeakError> { + response + .status(200) + .set_header("content-type", "text/plain; charset=utf-8") + .end("Hello, World!") +} + +async fn json(_: &mut CpeakRequest, response: &mut CpeakResponse) -> Result<(), CpeakError> { + response + .status(200) + .set_header("content-type", "application/json") + .end("{\"message\":\"Hello, World!\"}") } diff --git a/frameworks/web-server/rust/ntex/Cargo.lock b/frameworks/web-server/rust/ntex/Cargo.lock new file mode 100644 index 0000000..dfffcce --- /dev/null +++ b/frameworks/web-server/rust/ntex/Cargo.lock @@ -0,0 +1,1110 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core_affinity" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a034b3a7b624016c6e13f5df875747cc25f884156aad2abd12b6c46797971342" +dependencies = [ + "libc", + "num_cpus", + "winapi", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix", + "windows-sys 0.61.2", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "env_filter", + "log", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "nanorand" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "ntex" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e231c76ec0d9e4581403f491cbe57721602b1d653c535c3c6da007db02840c49" +dependencies = [ + "base64", + "bitflags", + "encoding_rs", + "env_logger", + "httparse", + "httpdate", + "log", + "mime", + "nanorand", + "ntex-bytes", + "ntex-codec", + "ntex-h2", + "ntex-http", + "ntex-io", + "ntex-macros", + "ntex-net", + "ntex-router", + "ntex-rt", + "ntex-server", + "ntex-service", + "ntex-tls", + "ntex-util", + "percent-encoding", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "sha1", + "thiserror", + "variadics_please", +] + +[[package]] +name = "ntex-benchmark" +version = "0.1.0" +dependencies = [ + "ntex", +] + +[[package]] +name = "ntex-bytes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da68b58d622475c69e4f19e33d12f90b4acaff1939f134e09e9318ad8bc6fa74" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "serde", +] + +[[package]] +name = "ntex-codec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a7e111d946bb915d712df496728ca2a120b1b5643f66c580f13023bce46fda" +dependencies = [ + "ntex-bytes", +] + +[[package]] +name = "ntex-h2" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695ca21edca7c03ddcd1a450a8ec2efaa8fc937d8ab0ae8a7dd07fe2805d328f" +dependencies = [ + "ahash", + "bitflags", + "log", + "nanorand", + "ntex-bytes", + "ntex-codec", + "ntex-http", + "ntex-io", + "ntex-net", + "ntex-service", + "ntex-util", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "ntex-http" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61da3d6c8bec83c5481d7e36ed4cf1a8cd0edee3e2fa411290932b17549d5cf2" +dependencies = [ + "ahash", + "futures-core", + "http", + "itoa", + "log", + "ntex-bytes", + "serde", + "thiserror", +] + +[[package]] +name = "ntex-io" +version = "2.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dee52f78bf207359e5013f40bd959022eab8299608ac4750408fed80d7bdd4a" +dependencies = [ + "bitflags", + "log", + "ntex-bytes", + "ntex-codec", + "ntex-service", + "ntex-util", + "pin-project-lite", +] + +[[package]] +name = "ntex-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7389855b7cf0a7cc4cd6748b6d31ad8d45481c9a4d6c977d289a469a362f7766" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ntex-net" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8954ded069d59412df14b4c4804c4c6b45631300fcd13376c08aa19b4043d3fd" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "log", + "ntex-bytes", + "ntex-http", + "ntex-io", + "ntex-rt", + "ntex-service", + "ntex-util", + "thiserror", +] + +[[package]] +name = "ntex-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9c68c26a87ffca54339be5f95223339db3e7bcc5d64733fef20812970a746f" +dependencies = [ + "http", + "log", + "ntex-bytes", + "regex", + "serde", +] + +[[package]] +name = "ntex-rt" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86073cb8a4ae37ca4d4a10a50fb8fc01e7a40973567d3e4f1dcbfb37039a97ce" +dependencies = [ + "async-channel", + "futures-timer", + "log", + "oneshot", +] + +[[package]] +name = "ntex-server" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b886e739e5101ba06f083244bda0557997521c3ddf9b8f85ca74bc2aa165aa29" +dependencies = [ + "async-channel", + "atomic-waker", + "core_affinity", + "ctrlc", + "log", + "ntex-bytes", + "ntex-net", + "ntex-rt", + "ntex-service", + "ntex-util", + "oneshot", + "polling", + "signal-hook", + "socket2", +] + +[[package]] +name = "ntex-service" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5369e31c09d793420b2410431852df0bbb8d19784c2c3696afa9e58e1217718" +dependencies = [ + "foldhash", + "log", + "slab", +] + +[[package]] +name = "ntex-tls" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e08d45e746fe033db0b92dd6b7b1741ebd7ba04f8ce8bb3da38e330950a131c" +dependencies = [ + "log", + "ntex-bytes", + "ntex-io", + "ntex-net", + "ntex-service", + "ntex-util", +] + +[[package]] +name = "ntex-util" +version = "2.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9511ba3ddba94f75b53c47faede99d624d9dd53baf8e706ffafc6f760c1b923d" +dependencies = [ + "ahash", + "bitflags", + "futures-core", + "futures-timer", + "log", + "ntex-bytes", + "ntex-rt", + "ntex-service", + "pin-project-lite", + "slab", + "thiserror", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oneshot" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/frameworks/web-server/rust/ntex/Cargo.toml b/frameworks/web-server/rust/ntex/Cargo.toml index 19aa114..a4d59ca 100644 --- a/frameworks/web-server/rust/ntex/Cargo.toml +++ b/frameworks/web-server/rust/ntex/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] ntex = "2.8" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" + +[workspace] diff --git a/frameworks/web-server/rust/ntex/Dockerfile.glibc b/frameworks/web-server/rust/ntex/Dockerfile.glibc index a2e7497..5cfa1d8 100644 --- a/frameworks/web-server/rust/ntex/Dockerfile.glibc +++ b/frameworks/web-server/rust/ntex/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM rust:1.85-bookworm AS builder +FROM rust:1.88-bookworm AS builder WORKDIR /usr/src/app COPY . . RUN cargo build --release diff --git a/frameworks/web-server/rust/ntex/Dockerfile.musl b/frameworks/web-server/rust/ntex/Dockerfile.musl index 80418a6..eeed32a 100644 --- a/frameworks/web-server/rust/ntex/Dockerfile.musl +++ b/frameworks/web-server/rust/ntex/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM rust:1.85-alpine3.21 AS builder +FROM rust:1.88-alpine3.21 AS builder WORKDIR /usr/src/app RUN apk add --no-cache musl-dev COPY . . diff --git a/frameworks/web-server/rust/ntex/framework.toml b/frameworks/web-server/rust/ntex/framework.toml index 6a7e3e5..fdbba64 100644 --- a/frameworks/web-server/rust/ntex/framework.toml +++ b/frameworks/web-server/rust/ntex/framework.toml @@ -2,7 +2,7 @@ name = "ntex" version = "2.8.0" language = "rust" -language_version = "1.85" +language_version = "1.88" website = "https://github.com/ntex-rs/ntex" category = "web-server" tier = "micro" diff --git a/frameworks/web-server/rust/ntex/src/main.rs b/frameworks/web-server/rust/ntex/src/main.rs index 3bafeef..1013118 100644 --- a/frameworks/web-server/rust/ntex/src/main.rs +++ b/frameworks/web-server/rust/ntex/src/main.rs @@ -1,10 +1,6 @@ use ntex::web::{self, App, HttpResponse, HttpServer, Responder}; -use serde::Serialize; -#[derive(Serialize)] -struct Message { - message: &'static str, -} +const JSON: &[u8] = b"{\"message\":\"Hello, World!\"}"; #[web::get("/healthz")] async fn healthz() -> impl Responder { @@ -20,9 +16,9 @@ async fn plaintext() -> impl Responder { #[web::get("/json")] async fn json() -> impl Responder { - web::types::Json(Message { - message: "Hello, World!", - }) + HttpResponse::Ok() + .content_type("application/json") + .body(JSON) } #[ntex::main] diff --git a/frameworks/web-server/zig/dusty/Dockerfile.glibc b/frameworks/web-server/zig/dusty/Dockerfile.glibc index f358a22..acaa6a1 100644 --- a/frameworks/web-server/zig/dusty/Dockerfile.glibc +++ b/frameworks/web-server/zig/dusty/Dockerfile.glibc @@ -1,9 +1,10 @@ FROM debian:bookworm-slim AS builder WORKDIR /usr/src/app -RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl xz-utils && rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig +RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl git xz-utils && rm -rf /var/lib/apt/lists/* +RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig +RUN git init /opt/dusty && git -C /opt/dusty remote add origin https://github.com/lalinsky/dusty.git && git -C /opt/dusty fetch --depth 1 origin 494f856d1795cfbf6180f6f8e5a80665d8114174 && git -C /opt/dusty checkout --detach FETCH_HEAD COPY . . -RUN zig build -Doptimize=ReleaseFast +RUN ln -s /opt/dusty dusty && zig build -Doptimize=ReleaseFast FROM debian:bookworm-slim WORKDIR /app diff --git a/frameworks/web-server/zig/dusty/Dockerfile.musl b/frameworks/web-server/zig/dusty/Dockerfile.musl index 78294df..d9b6fd8 100644 --- a/frameworks/web-server/zig/dusty/Dockerfile.musl +++ b/frameworks/web-server/zig/dusty/Dockerfile.musl @@ -1,9 +1,10 @@ FROM alpine:3.21 AS builder WORKDIR /usr/src/app -RUN apk add --no-cache ca-certificates curl xz -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig +RUN apk add --no-cache ca-certificates curl git xz +RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig +RUN git init /opt/dusty && git -C /opt/dusty remote add origin https://github.com/lalinsky/dusty.git && git -C /opt/dusty fetch --depth 1 origin 494f856d1795cfbf6180f6f8e5a80665d8114174 && git -C /opt/dusty checkout --detach FETCH_HEAD COPY . . -RUN zig build -Doptimize=ReleaseFast +RUN ln -s /opt/dusty dusty && zig build -Doptimize=ReleaseFast FROM alpine:3.21 WORKDIR /app diff --git a/frameworks/web-server/zig/dusty/build.zig b/frameworks/web-server/zig/dusty/build.zig index 24bd437..c797ac5 100644 --- a/frameworks/web-server/zig/dusty/build.zig +++ b/frameworks/web-server/zig/dusty/build.zig @@ -6,10 +6,19 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "server", - .root_source_file = b.path("src/main.zig"), + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + const dusty = b.dependency("dusty", .{ .target = target, .optimize = optimize, + .use_tls = false, }); + exe.root_module.addImport("dusty", dusty.module("dusty")); b.installArtifact(exe); } diff --git a/frameworks/web-server/zig/dusty/build.zig.zon b/frameworks/web-server/zig/dusty/build.zig.zon index 9736c21..76b9266 100644 --- a/frameworks/web-server/zig/dusty/build.zig.zon +++ b/frameworks/web-server/zig/dusty/build.zig.zon @@ -1,7 +1,11 @@ .{ - .name = "dusty", - .version = "0.1.0", - .dependencies = .{}, + .name = .dusty_benchmark, + .version = "0.3.1", + .fingerprint = 0xcd5ab96d27d8695d, + .minimum_zig_version = "0.16.0", + .dependencies = .{ + .dusty = .{ .path = "dusty" }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/frameworks/web-server/zig/dusty/framework.toml b/frameworks/web-server/zig/dusty/framework.toml index 916ac87..43dabf1 100644 --- a/frameworks/web-server/zig/dusty/framework.toml +++ b/frameworks/web-server/zig/dusty/framework.toml @@ -1,8 +1,8 @@ [framework] name = "dusty" -version = "0.1.0" +version = "0.3.1" language = "zig" -language_version = "0.13.0" +language_version = "0.16.0" website = "https://github.com/lalinsky/dusty" category = "web-server" tier = "micro" diff --git a/frameworks/web-server/zig/dusty/src/main.zig b/frameworks/web-server/zig/dusty/src/main.zig index 4ac394f..43a430a 100644 --- a/frameworks/web-server/zig/dusty/src/main.zig +++ b/frameworks/web-server/zig/dusty/src/main.zig @@ -1,37 +1,32 @@ const std = @import("std"); -const net = std.net; +const http = @import("dusty"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); +fn health(_: *http.Request, res: *http.Response) !void { + res.status = .ok; + res.body = ""; +} - const address = try net.Address.parseIp4("0.0.0.0", 8080); - var server = try address.listen(.{ .reuse_address = true }); - defer server.deinit(); +fn plaintext(_: *http.Request, res: *http.Response) !void { + res.status = .ok; + res.body = "Hello, World!"; +} - while (true) { - const connection = try server.accept(); - try handleConnection(connection); - } +fn json(_: *http.Request, res: *http.Response) !void { + res.status = .ok; + res.content_type = .json; + res.body = "{\"message\":\"Hello, World!\"}"; } -fn handleConnection(conn: net.Server.Connection) !void { - defer conn.stream.close(); - var buf: [1024]u8 = undefined; - const bytes_read = try conn.stream.read(&buf); - if (bytes_read == 0) return; +pub fn main(init: std.process.Init) !void { + var server = http.Server(void).init(init.gpa, init.io, .{}, {}); + defer server.deinit(); + + server.router.get("/healthz", health); + server.router.get("/plaintext", plaintext); + server.router.get("/json", json); - const req = buf[0..bytes_read]; - if (std.mem.startsWith(u8, req, "GET /healthz")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); - } else if (std.mem.startsWith(u8, req, "GET /plaintext")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"); - } else if (std.mem.startsWith(u8, req, "GET /json")) { - const json = "{\"message\": \"Hello, World!\"}"; - var header_buf: [128]u8 = undefined; - const resp = try std.fmt.bufPrint(&header_buf, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {d}\r\n\r\n{s}", .{ json.len, json }); - _ = try conn.stream.write(resp); - } else { - _ = try conn.stream.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); - } + const address: http.Address = .{ + .ip = try std.Io.net.IpAddress.parse("0.0.0.0", 8080), + }; + try server.listen(address); } diff --git a/frameworks/web-server/zig/swerver/Dockerfile.glibc b/frameworks/web-server/zig/swerver/Dockerfile.glibc index f358a22..ca6a142 100644 --- a/frameworks/web-server/zig/swerver/Dockerfile.glibc +++ b/frameworks/web-server/zig/swerver/Dockerfile.glibc @@ -1,13 +1,14 @@ FROM debian:bookworm-slim AS builder WORKDIR /usr/src/app -RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl xz-utils && rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig -COPY . . -RUN zig build -Doptimize=ReleaseFast +RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl git xz-utils zlib1g-dev && rm -rf /var/lib/apt/lists/* +RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig +RUN git init /opt/swerver && git -C /opt/swerver remote add origin https://github.com/justinGrosvenor/swerver.git && git -C /opt/swerver fetch --depth 1 origin a4e469301d4a47fb3f4552b7cbd58d135fac8c9a && git -C /opt/swerver checkout --detach FETCH_HEAD +COPY src/main.zig /opt/swerver/src/main.zig +RUN cd /opt/swerver && zig build -Doptimize=ReleaseFast FROM debian:bookworm-slim WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* -COPY --from=builder /usr/src/app/zig-out/bin/server /app/server +COPY --from=builder /opt/swerver/zig-out/bin/swerver /app/server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/zig/swerver/Dockerfile.musl b/frameworks/web-server/zig/swerver/Dockerfile.musl index 78294df..7c66c70 100644 --- a/frameworks/web-server/zig/swerver/Dockerfile.musl +++ b/frameworks/web-server/zig/swerver/Dockerfile.musl @@ -1,13 +1,14 @@ FROM alpine:3.21 AS builder WORKDIR /usr/src/app -RUN apk add --no-cache ca-certificates curl xz -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig -COPY . . -RUN zig build -Doptimize=ReleaseFast +RUN apk add --no-cache ca-certificates curl git xz zlib-dev +RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig +RUN git init /opt/swerver && git -C /opt/swerver remote add origin https://github.com/justinGrosvenor/swerver.git && git -C /opt/swerver fetch --depth 1 origin a4e469301d4a47fb3f4552b7cbd58d135fac8c9a && git -C /opt/swerver checkout --detach FETCH_HEAD +COPY src/main.zig /opt/swerver/src/main.zig +RUN cd /opt/swerver && zig build -Doptimize=ReleaseFast FROM alpine:3.21 WORKDIR /app RUN apk add --no-cache ca-certificates curl -COPY --from=builder /usr/src/app/zig-out/bin/server /app/server +COPY --from=builder /opt/swerver/zig-out/bin/swerver /app/server EXPOSE 8080 ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/zig/swerver/build.zig b/frameworks/web-server/zig/swerver/build.zig deleted file mode 100644 index 24bd437..0000000 --- a/frameworks/web-server/zig/swerver/build.zig +++ /dev/null @@ -1,15 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const exe = b.addExecutable(.{ - .name = "server", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - b.installArtifact(exe); -} diff --git a/frameworks/web-server/zig/swerver/build.zig.zon b/frameworks/web-server/zig/swerver/build.zig.zon deleted file mode 100644 index dda5dee..0000000 --- a/frameworks/web-server/zig/swerver/build.zig.zon +++ /dev/null @@ -1,10 +0,0 @@ -.{ - .name = "swerver", - .version = "0.1.0", - .dependencies = .{}, - .paths = .{ - "build.zig", - "build.zig.zon", - "src", - }, -} diff --git a/frameworks/web-server/zig/swerver/framework.toml b/frameworks/web-server/zig/swerver/framework.toml index 145a52a..d8a3931 100644 --- a/frameworks/web-server/zig/swerver/framework.toml +++ b/frameworks/web-server/zig/swerver/framework.toml @@ -1,8 +1,8 @@ [framework] name = "swerver" -version = "0.1.0" +version = "0.1.0-alpha.34" language = "zig" -language_version = "0.13.0" +language_version = "0.16.0" website = "https://github.com/justinGrosvenor/swerver" category = "web-server" tier = "micro" diff --git a/frameworks/web-server/zig/swerver/src/main.zig b/frameworks/web-server/zig/swerver/src/main.zig index 4ac394f..bc60921 100644 --- a/frameworks/web-server/zig/swerver/src/main.zig +++ b/frameworks/web-server/zig/swerver/src/main.zig @@ -1,37 +1,34 @@ const std = @import("std"); -const net = std.net; +const swerver = @import("swerver"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); +fn health(ctx: *swerver.router.HandlerContext) swerver.response.Response { + return ctx.text(200, ""); +} - const address = try net.Address.parseIp4("0.0.0.0", 8080); - var server = try address.listen(.{ .reuse_address = true }); - defer server.deinit(); +fn plaintext(ctx: *swerver.router.HandlerContext) swerver.response.Response { + return ctx.text(200, "Hello, World!"); +} - while (true) { - const connection = try server.accept(); - try handleConnection(connection); - } +fn json(ctx: *swerver.router.HandlerContext) swerver.response.Response { + return ctx.json(200, "{\"message\":\"Hello, World!\"}"); } -fn handleConnection(conn: net.Server.Connection) !void { - defer conn.stream.close(); - var buf: [1024]u8 = undefined; - const bytes_read = try conn.stream.read(&buf); - if (bytes_read == 0) return; +pub fn main() !void { + var router = swerver.router.Router.init(.{}); + try router.get("/healthz", health); + try router.get("/plaintext", plaintext); + try router.get("/json", json); - const req = buf[0..bytes_read]; - if (std.mem.startsWith(u8, req, "GET /healthz")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); - } else if (std.mem.startsWith(u8, req, "GET /plaintext")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"); - } else if (std.mem.startsWith(u8, req, "GET /json")) { - const json = "{\"message\": \"Hello, World!\"}"; - var header_buf: [128]u8 = undefined; - const resp = try std.fmt.bufPrint(&header_buf, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {d}\r\n\r\n{s}", .{ json.len, json }); - _ = try conn.stream.write(resp); - } else { - _ = try conn.stream.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); + var config = swerver.config.ServerConfig.default(); + config.disable_middleware = true; + config.disable_preencoded = false; + const server = try swerver.ServerBuilder + .config(config) + .router(router) + .build(std.heap.smp_allocator); + defer { + server.deinit(); + std.heap.smp_allocator.destroy(server); } + try server.run(null); } diff --git a/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc b/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc index f358a22..2d4c13d 100644 --- a/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc +++ b/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc @@ -1,8 +1,10 @@ FROM debian:bookworm-slim AS builder WORKDIR /usr/src/app -RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl xz-utils && rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig +RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl git xz-utils && rm -rf /var/lib/apt/lists/* +RUN curl -sSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig +RUN git config --global url."https://github.com/".insteadOf git@github.com: && git init /opt/zuws && git -C /opt/zuws remote add origin https://github.com/harmony-co/zuws.git && git -C /opt/zuws fetch --depth 1 origin cb360ea5cf6706660b60ba5e79ea3b0a61899cc6 && git -C /opt/zuws checkout --detach FETCH_HEAD && git -C /opt/zuws submodule update --init --depth 1 --recursive && sed -i 's/return c.uws_ws(self, pattern, b);/return c.uws_ws(self, @ptrCast(pattern), b);/' /opt/zuws/src/app.zig && sed -i 's/)(self, pattern, handler);/)(self, @ptrCast(pattern), handler);/' /opt/zuws/src/app.zig COPY . . +RUN ln -s /opt/zuws zuws RUN zig build -Doptimize=ReleaseFast FROM debian:bookworm-slim diff --git a/frameworks/web-server/zig/uwebzockets/Dockerfile.musl b/frameworks/web-server/zig/uwebzockets/Dockerfile.musl index 78294df..48f5346 100644 --- a/frameworks/web-server/zig/uwebzockets/Dockerfile.musl +++ b/frameworks/web-server/zig/uwebzockets/Dockerfile.musl @@ -1,8 +1,10 @@ FROM alpine:3.21 AS builder WORKDIR /usr/src/app -RUN apk add --no-cache ca-certificates curl xz -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig +RUN apk add --no-cache ca-certificates curl git xz +RUN curl -sSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig +RUN git config --global url."https://github.com/".insteadOf git@github.com: && git init /opt/zuws && git -C /opt/zuws remote add origin https://github.com/harmony-co/zuws.git && git -C /opt/zuws fetch --depth 1 origin cb360ea5cf6706660b60ba5e79ea3b0a61899cc6 && git -C /opt/zuws checkout --detach FETCH_HEAD && git -C /opt/zuws submodule update --init --depth 1 --recursive && sed -i 's/return c.uws_ws(self, pattern, b);/return c.uws_ws(self, @ptrCast(pattern), b);/' /opt/zuws/src/app.zig && sed -i 's/)(self, pattern, handler);/)(self, @ptrCast(pattern), handler);/' /opt/zuws/src/app.zig COPY . . +RUN ln -s /opt/zuws zuws RUN zig build -Doptimize=ReleaseFast FROM alpine:3.21 diff --git a/frameworks/web-server/zig/uwebzockets/build.zig b/frameworks/web-server/zig/uwebzockets/build.zig index 24bd437..3ad6589 100644 --- a/frameworks/web-server/zig/uwebzockets/build.zig +++ b/frameworks/web-server/zig/uwebzockets/build.zig @@ -6,10 +6,22 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "server", - .root_source_file = b.path("src/main.zig"), + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + const zuws = b.dependency("zuws", .{ .target = target, .optimize = optimize, + .debug_logs = false, + .ssl = false, + .no_zlib = true, + .with_proxy = false, + .with_uv = false, }); + exe.root_module.addImport("zuws", zuws.module("zuws")); b.installArtifact(exe); } diff --git a/frameworks/web-server/zig/uwebzockets/build.zig.zon b/frameworks/web-server/zig/uwebzockets/build.zig.zon index 965a3ec..92d7dcc 100644 --- a/frameworks/web-server/zig/uwebzockets/build.zig.zon +++ b/frameworks/web-server/zig/uwebzockets/build.zig.zon @@ -1,7 +1,10 @@ .{ - .name = "uwebzockets", - .version = "0.1.0", - .dependencies = .{}, + .name = .uwebzockets, + .version = "0.4.2", + .fingerprint = 0x3b11685b539bfa31, + .dependencies = .{ + .zuws = .{ .path = "zuws" }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/frameworks/web-server/zig/uwebzockets/framework.toml b/frameworks/web-server/zig/uwebzockets/framework.toml index ee3128e..5b4ee2a 100644 --- a/frameworks/web-server/zig/uwebzockets/framework.toml +++ b/frameworks/web-server/zig/uwebzockets/framework.toml @@ -1,8 +1,8 @@ [framework] name = "uwebzockets" -version = "0.1.0" +version = "0.4.2" language = "zig" -language_version = "0.13.0" +language_version = "0.16.0" website = "https://github.com/farbenbuilds/uWebZockets" category = "web-server" tier = "micro" @@ -14,7 +14,7 @@ db_single = false db_multi = false fortunes = false updates = false -websocket = false +websocket = true http2 = false http3 = false diff --git a/frameworks/web-server/zig/uwebzockets/src/main.zig b/frameworks/web-server/zig/uwebzockets/src/main.zig index 4ac394f..3c0b522 100644 --- a/frameworks/web-server/zig/uwebzockets/src/main.zig +++ b/frameworks/web-server/zig/uwebzockets/src/main.zig @@ -1,37 +1,47 @@ -const std = @import("std"); -const net = std.net; +const zuws = @import("zuws"); +const App = zuws.App; +const Request = zuws.Request; +const Response = zuws.Response; +const WebSocket = zuws.WebSocket; pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); + const app = try App.init(); + defer app.deinit(); - const address = try net.Address.parseIp4("0.0.0.0", 8080); - var server = try address.listen(.{ .reuse_address = true }); - defer server.deinit(); + _ = app.get("/healthz", healthz); + _ = app.get("/plaintext", plaintext); + _ = app.get("/json", json); + _ = app.ws("/ws", .{ .upgrade = upgrade, .message = echo }); + app.listen(8080, null); + app.run(); +} + +fn healthz(res: *Response, _: *Request) callconv(.c) void { + res.end("", false); +} + +fn plaintext(res: *Response, _: *Request) callconv(.c) void { + res.writeHeader("content-type", "text/plain; charset=utf-8"); + res.end("Hello, World!", false); +} + +fn json(res: *Response, _: *Request) callconv(.c) void { + res.writeHeader("content-type", "application/json"); + res.end("{\"message\":\"Hello, World!\"}", false); +} + +fn upgrade(res: *Response, req: *Request, ctx: *zuws.c.SocketContext) callconv(.c) void { + const key = req.getHeader("sec-websocket-key") orelse return bad_request(res); + const protocol = req.getHeader("sec-websocket-protocol") orelse ""; + const extensions = req.getHeader("sec-websocket-extensions") orelse ""; + zuws.c.uws_res_upgrade(res, null, key.ptr, key.len, protocol.ptr, protocol.len, extensions.ptr, extensions.len, ctx); +} - while (true) { - const connection = try server.accept(); - try handleConnection(connection); - } +fn bad_request(res: *Response) void { + res.writeStatusCodeWithText(.BadRequest); + res.endWithoutBody(true); } -fn handleConnection(conn: net.Server.Connection) !void { - defer conn.stream.close(); - var buf: [1024]u8 = undefined; - const bytes_read = try conn.stream.read(&buf); - if (bytes_read == 0) return; - - const req = buf[0..bytes_read]; - if (std.mem.startsWith(u8, req, "GET /healthz")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); - } else if (std.mem.startsWith(u8, req, "GET /plaintext")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"); - } else if (std.mem.startsWith(u8, req, "GET /json")) { - const json = "{\"message\": \"Hello, World!\"}"; - var header_buf: [128]u8 = undefined; - const resp = try std.fmt.bufPrint(&header_buf, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {d}\r\n\r\n{s}", .{ json.len, json }); - _ = try conn.stream.write(resp); - } else { - _ = try conn.stream.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); - } +fn echo(ws: *WebSocket, message: []const u8, opcode: zuws.c.Opcode) void { + _ = ws.send(message, opcode); } diff --git a/frameworks/web-server/zig/zix/Dockerfile.glibc b/frameworks/web-server/zig/zix/Dockerfile.glibc deleted file mode 100644 index f358a22..0000000 --- a/frameworks/web-server/zig/zix/Dockerfile.glibc +++ /dev/null @@ -1,13 +0,0 @@ -FROM debian:bookworm-slim AS builder -WORKDIR /usr/src/app -RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl xz-utils && rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig -COPY . . -RUN zig build -Doptimize=ReleaseFast - -FROM debian:bookworm-slim -WORKDIR /app -RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* -COPY --from=builder /usr/src/app/zig-out/bin/server /app/server -EXPOSE 8080 -ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/zig/zix/Dockerfile.musl b/frameworks/web-server/zig/zix/Dockerfile.musl deleted file mode 100644 index 78294df..0000000 --- a/frameworks/web-server/zig/zix/Dockerfile.musl +++ /dev/null @@ -1,13 +0,0 @@ -FROM alpine:3.21 AS builder -WORKDIR /usr/src/app -RUN apk add --no-cache ca-certificates curl xz -RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig -COPY . . -RUN zig build -Doptimize=ReleaseFast - -FROM alpine:3.21 -WORKDIR /app -RUN apk add --no-cache ca-certificates curl -COPY --from=builder /usr/src/app/zig-out/bin/server /app/server -EXPOSE 8080 -ENTRYPOINT ["/app/server"] diff --git a/frameworks/web-server/zig/zix/build.zig b/frameworks/web-server/zig/zix/build.zig deleted file mode 100644 index 24bd437..0000000 --- a/frameworks/web-server/zig/zix/build.zig +++ /dev/null @@ -1,15 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const exe = b.addExecutable(.{ - .name = "server", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - b.installArtifact(exe); -} diff --git a/frameworks/web-server/zig/zix/build.zig.zon b/frameworks/web-server/zig/zix/build.zig.zon deleted file mode 100644 index f98ab18..0000000 --- a/frameworks/web-server/zig/zix/build.zig.zon +++ /dev/null @@ -1,10 +0,0 @@ -.{ - .name = "zix", - .version = "0.1.0", - .dependencies = .{}, - .paths = .{ - "build.zig", - "build.zig.zon", - "src", - }, -} diff --git a/frameworks/web-server/zig/zix/framework.toml b/frameworks/web-server/zig/zix/framework.toml deleted file mode 100644 index 5774255..0000000 --- a/frameworks/web-server/zig/zix/framework.toml +++ /dev/null @@ -1,31 +0,0 @@ -[framework] -name = "zix" -version = "0.1.0" -language = "zig" -language_version = "0.13.0" -website = "https://github.com/prothegee/zix" -category = "web-server" -tier = "micro" - -[capabilities] -plaintext = true -json = true -db_single = false -db_multi = false -fortunes = false -updates = false -websocket = false -http2 = false -http3 = false - -[build] -engine = "docker" - -[build.docker] -dockerfile_glibc = "Dockerfile.glibc" -dockerfile_musl = "Dockerfile.musl" - -[runtime] -port = 8080 -healthcheck_path = "/healthz" -startup_timeout_seconds = 10 diff --git a/frameworks/web-server/zig/zix/src/main.zig b/frameworks/web-server/zig/zix/src/main.zig deleted file mode 100644 index 4ac394f..0000000 --- a/frameworks/web-server/zig/zix/src/main.zig +++ /dev/null @@ -1,37 +0,0 @@ -const std = @import("std"); -const net = std.net; - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - - const address = try net.Address.parseIp4("0.0.0.0", 8080); - var server = try address.listen(.{ .reuse_address = true }); - defer server.deinit(); - - while (true) { - const connection = try server.accept(); - try handleConnection(connection); - } -} - -fn handleConnection(conn: net.Server.Connection) !void { - defer conn.stream.close(); - var buf: [1024]u8 = undefined; - const bytes_read = try conn.stream.read(&buf); - if (bytes_read == 0) return; - - const req = buf[0..bytes_read]; - if (std.mem.startsWith(u8, req, "GET /healthz")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); - } else if (std.mem.startsWith(u8, req, "GET /plaintext")) { - _ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 13\r\n\r\nHello, World!"); - } else if (std.mem.startsWith(u8, req, "GET /json")) { - const json = "{\"message\": \"Hello, World!\"}"; - var header_buf: [128]u8 = undefined; - const resp = try std.fmt.bufPrint(&header_buf, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {d}\r\n\r\n{s}", .{ json.len, json }); - _ = try conn.stream.write(resp); - } else { - _ = try conn.stream.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); - } -} diff --git a/scripts/build_container.sh b/scripts/build_container.sh index 7e955b6..a00c9a6 100644 --- a/scripts/build_container.sh +++ b/scripts/build_container.sh @@ -9,6 +9,7 @@ fi CONTEXT_DIR="$1" TAG="$2" TARGET_LIBC="${3:-glibc}" +CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" DOCKERFILE="${CONTEXT_DIR}/Dockerfile.${TARGET_LIBC}" @@ -17,9 +18,14 @@ if [ ! -f "$DOCKERFILE" ]; then exit 1 fi -echo "Building Docker image: ${TAG} (${TARGET_LIBC}) without cache drift..." +if ! command -v "$CONTAINER_RUNTIME" >/dev/null 2>&1; then + echo "Container runtime not found: $CONTAINER_RUNTIME" >&2 + exit 1 +fi + +echo "Building container image: ${TAG} (${TARGET_LIBC}) without cache drift..." -docker build \ +"$CONTAINER_RUNTIME" build \ --no-cache \ --pull \ --tag "${TAG}" \ diff --git a/scripts/check_conventions.sh b/scripts/check_conventions.sh index 0979bf3..8ed2315 100644 --- a/scripts/check_conventions.sh +++ b/scripts/check_conventions.sh @@ -26,7 +26,7 @@ fi echo "[2/4] Checking for files exceeding 200 lines..." # Exclude lockfiles and vendored agent skills -long_files=$(git ls-files | grep -v '^\.agents/skills/' | grep -v 'skills-lock\.json$' | grep -v 'Cargo\.lock$' | while read -r file; do +long_files=$(git ls-files | grep -Ev '^\.agents/skills/|skills-lock\.json$|Cargo\.lock$|package-lock\.json$' | while read -r file; do if [ -f "$file" ]; then lines=$(wc -l < "$file") if [ "$lines" -gt 200 ]; then @@ -44,7 +44,7 @@ else fi echo "[3/4] Checking for slop markers in source files..." -# Exclude auditor scripts and TODO.md from slop pattern search +# Exclude the auditor from its own marker search slop_matches=$(git ls-files '*.rs' '*.sh' '*.toml' '*.yml' '*.yaml' | grep -v '^scripts/check_conventions\.' | while read -r file; do if [ -f "$file" ]; then if grep -nE '\b(FIXME|XXX|HACK)\b' "$file" >/dev/null 2>&1; then diff --git a/scripts/cleanup_environment.sh b/scripts/cleanup_environment.sh new file mode 100755 index 0000000..1d0bf22 --- /dev/null +++ b/scripts/cleanup_environment.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) +REPO_ROOT=$(CDPATH= cd "$SCRIPT_DIR/.." && pwd) +STATE_DIR=${BENCHMARK_STATE_DIR:-/var/lib/framework-benchmark} +RUNTIME=${CONTAINER_RUNTIME:-docker} +APPLY=0 + +compose() { + if "$RUNTIME" compose version >/dev/null 2>&1; then + "$RUNTIME" compose "$@" + return + fi + if [ "$RUNTIME" = docker ] && command -v docker-compose >/dev/null 2>&1; then + docker-compose "$@" + return + fi + echo "No Compose plugin found for $RUNTIME." >&2 + return 1 +} + +[ "${1:-}" = "--apply" ] && APPLY=1 +if [ "$APPLY" -eq 0 ]; then + echo "Dry run. Re-run with --apply to remove benchmark resources and restore saved sysctls." + exit 0 +fi + +if ! command -v "$RUNTIME" >/dev/null 2>&1; then + echo "Container runtime not found: $RUNTIME" >&2 + exit 1 +fi + +containers=$("$RUNTIME" ps -aq --filter label=framework-benchmark.managed=true) +if [ -n "$containers" ]; then + "$RUNTIME" rm -f $containers +fi + +if [ "${BENCHMARK_PURGE_DATABASE:-0}" = 1 ]; then + compose -f "$REPO_ROOT/deploy/postgres/compose.yaml" down -v --remove-orphans +else + compose -f "$REPO_ROOT/deploy/postgres/compose.yaml" down --remove-orphans +fi + +if [ -f "$STATE_DIR/sysctl.before" ]; then + if [ "$(id -u)" -ne 0 ]; then + echo "Run as root to restore saved sysctl values." >&2 + exit 1 + fi + while IFS='=' read -r key value; do + sysctl -w "$key=$value" + done < "$STATE_DIR/sysctl.before" + rm -f "$STATE_DIR/sysctl.before" +fi + +if [ "${BENCHMARK_PRUNE:-0}" = 1 ]; then + "$RUNTIME" image prune -f +fi + +echo "Benchmark environment cleanup complete." diff --git a/scripts/collect_ebpf.sh b/scripts/collect_ebpf.sh new file mode 100755 index 0000000..99d5cb3 --- /dev/null +++ b/scripts/collect_ebpf.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh +set -eu + +PID=${1:?usage: collect_ebpf.sh PID DURATION_SECS OUTPUT_FILE} +DURATION=${2:?usage: collect_ebpf.sh PID DURATION_SECS OUTPUT_FILE} +OUTPUT=${3:?usage: collect_ebpf.sh PID DURATION_SECS OUTPUT_FILE} +SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) + +case $PID in + *[!0-9]* | '') echo "PID must be numeric." >&2; exit 2 ;; +esac +case $DURATION in + *[!0-9]* | '') echo "DURATION_SECS must be numeric." >&2; exit 2 ;; +esac + +if ! command -v bpftrace >/dev/null 2>&1; then + echo "bpftrace is required for eBPF collection." >&2 + exit 1 +fi + +mkdir -p "$(dirname "$OUTPUT")" +bpftrace -q -c "sleep $DURATION" "$SCRIPT_DIR/ebpf_metrics.bt" "$PID" > "$OUTPUT" diff --git a/scripts/ebpf_metrics.bt b/scripts/ebpf_metrics.bt new file mode 100644 index 0000000..0328dc8 --- /dev/null +++ b/scripts/ebpf_metrics.bt @@ -0,0 +1,36 @@ +#!/usr/bin/env bpftrace + +tracepoint:sched:sched_switch +/pid == $1/ +{ + @off_cpu_start[tid] = nsecs; + @context_switches = count(); +} + +tracepoint:sched:sched_switch +/@off_cpu_start[args->next_pid]/ +{ + $start = @off_cpu_start[args->next_pid]; + @off_cpu_usec = sum((nsecs - $start) / 1000); + delete(@off_cpu_start[args->next_pid]); +} + +kprobe:mark_page_accessed +/pid == $1/ +{ + @page_cache_hits = count(); +} + +kprobe:filemap_add_folio +/pid == $1/ +{ + @page_cache_misses = count(); +} + +END +{ + printf("off_cpu_usec %lld\n", @off_cpu_usec); + printf("page_cache_hits %lld\n", @page_cache_hits); + printf("page_cache_misses %lld\n", @page_cache_misses); + printf("context_switches %lld\n", @context_switches); +} diff --git a/scripts/run_autobahn_suite.sh b/scripts/run_autobahn_suite.sh new file mode 100755 index 0000000..0d0be32 --- /dev/null +++ b/scripts/run_autobahn_suite.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env sh +set -eu + +RUNTIME=${CONTAINER_RUNTIME:-docker} +IMAGE=${AUTOBAHN_IMAGE:-crossbario/autobahn-testsuite:25.10.1} +TARGET_URL=${AUTOBAHN_TARGET_URL:-ws://127.0.0.1:8080/ws} +RESULTS_DIR=${AUTOBAHN_RESULTS_DIR:-results/autobahn} +CONFIG=$(mktemp) + +cleanup() { + rm -f "$CONFIG" +} + +trap cleanup EXIT HUP INT TERM + +if ! command -v "$RUNTIME" >/dev/null 2>&1; then + echo "Container runtime not found: $RUNTIME" >&2 + exit 1 +fi + +mkdir -p "$RESULTS_DIR" +printf '%s\n' '{' > "$CONFIG" +printf ' "servers": [{"agent": "framework-benchmark", "url": "%s"}],\n' "$TARGET_URL" >> "$CONFIG" +printf '%s\n' ' "outdir": "/reports",' >> "$CONFIG" +printf '%s\n' ' "cases": ["*"],' >> "$CONFIG" +printf '%s\n' ' "exclude-cases": [],' >> "$CONFIG" +printf '%s\n' ' "exclude-agent-cases": {}' >> "$CONFIG" +printf '%s\n' '}' >> "$CONFIG" + +"$RUNTIME" run --rm --network host \ + --volume "$CONFIG:/config/fuzzingclient.json:ro" \ + --volume "$(cd "$RESULTS_DIR" && pwd):/reports" \ + "$IMAGE" wstest -m fuzzingclient -s /config/fuzzingclient.json diff --git a/scripts/run_benchmarks_all.sh b/scripts/run_benchmarks_all.sh new file mode 100755 index 0000000..c0b5e81 --- /dev/null +++ b/scripts/run_benchmarks_all.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) +REPO_ROOT=$(CDPATH= cd "$SCRIPT_DIR/.." && pwd) +RUNTIME=${CONTAINER_RUNTIME:-docker} +DATABASE_URL=${DATABASE_URL:-postgres://benchmarkdbuser:benchmarkdbpass@127.0.0.1:5432/hello_world} +BENCHMARK_WORKERS=${BENCHMARK_WORKERS:-$(getconf _NPROCESSORS_ONLN)} +LIBCS=${BENCHMARK_LIBCS:-"glibc musl"} +FAILED=0 +CURRENT_CONTAINER= + +compose() { + if "$RUNTIME" compose version >/dev/null 2>&1; then + "$RUNTIME" compose "$@" + return + fi + if [ "$RUNTIME" = docker ] && command -v docker-compose >/dev/null 2>&1; then + docker-compose "$@" + return + fi + echo "No Compose plugin found for $RUNTIME." >&2 + return 1 +} + +stop_current() { + [ -n "$CURRENT_CONTAINER" ] || return + "$RUNTIME" rm -f "$CURRENT_CONTAINER" >/dev/null 2>&1 || true + CURRENT_CONTAINER= +} + +wait_database() { + attempts=0 + while [ "$attempts" -lt 30 ]; do + database_id=$(compose -f "$REPO_ROOT/deploy/postgres/compose.yaml" ps -q postgres) + if [ -n "$database_id" ] && "$RUNTIME" exec "$database_id" \ + pg_isready -U "${POSTGRES_USER:-benchmarkdbuser}" -d "${POSTGRES_DB:-hello_world}" >/dev/null 2>&1; then + return + fi + attempts=$((attempts + 1)) + sleep 2 + done + echo "PostgreSQL did not become ready." >&2 + return 1 +} + +trap stop_current EXIT INT TERM + +if ! command -v "$RUNTIME" >/dev/null 2>&1; then + echo "Container runtime not found: $RUNTIME" >&2 + exit 1 +fi + +"$SCRIPT_DIR/validate_environment.sh" +if [ "${BENCHMARK_DATABASE_MANAGED:-1}" = 1 ]; then + compose -f "$REPO_ROOT/deploy/postgres/compose.yaml" up -d + wait_database +fi + +cd "$REPO_ROOT" +cargo build --release --bin orchestrator + +manifests=$(find frameworks -name framework.toml -print | sort) +for manifest in $manifests; do + context=$(dirname "$manifest") + name=$(awk -F '"' '/^name = / { print $2; exit }' "$manifest") + port=$(awk -F ' = ' '/^port = / { print $2; exit }' "$manifest") + [ -n "$name" ] && [ -n "$port" ] || continue + for libc in $LIBCS; do + tag="framework-benchmark/${name}:${libc}" + container="framework-benchmark-${name}-${libc}" + output="results/${name}/${libc}" + if ! CONTAINER_RUNTIME="$RUNTIME" "$SCRIPT_DIR/build_container.sh" "$context" "$tag" "$libc"; then + FAILED=1 + continue + fi + CURRENT_CONTAINER=$container + if ! "$RUNTIME" run -d --rm --name "$container" --network host \ + --label framework-benchmark.managed=true -e DATABASE_URL="$DATABASE_URL" \ + -e BENCHMARK_WORKERS="$BENCHMARK_WORKERS" "$tag"; then + FAILED=1 + CURRENT_CONTAINER= + continue + fi + if ! BENCHMARK_MANAGE_CONTAINER=0 BENCHMARK_LIBC="$libc" \ + "$REPO_ROOT/target/release/orchestrator" "$context" "$output"; then + FAILED=1 + fi + stop_current + done +done + +[ "$FAILED" -eq 0 ] || exit 1 +echo "All framework benchmark runs completed." diff --git a/scripts/setup_linux.sh b/scripts/setup_linux.sh new file mode 100755 index 0000000..143219d --- /dev/null +++ b/scripts/setup_linux.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env sh +set -eu + +STATE_DIR="${BENCHMARK_STATE_DIR:-/var/lib/framework-benchmark}" +APPLY=0 + +if [ "${1:-}" = "--apply" ]; then + APPLY=1 +fi + +if [ "$APPLY" -eq 0 ]; then + echo "Dry run. Re-run as root with --apply to install packages and tune the host." + exit 0 +fi + +if [ "$(id -u)" -ne 0 ]; then + echo "setup_linux.sh --apply must run as root." >&2 + exit 1 +fi + +install_apt() { + apt-get update + apt-get install -y build-essential ca-certificates curl git libssl-dev pkg-config \ + linux-tools-generic docker.io + apt-get install -y docker-compose-plugin || apt-get install -y docker-compose-v2 +} + +install_dnf() { + dnf install -y gcc gcc-c++ make ca-certificates curl git openssl-devel \ + perf docker docker-compose-plugin +} + +install_packages() { + if command -v apt-get >/dev/null 2>&1; then + install_apt + return + fi + if command -v dnf >/dev/null 2>&1; then + install_dnf + return + fi + echo "Unsupported package manager; install Docker, Rust, perf, and build tools manually." >&2 + exit 1 +} + +install_rust() { + if command -v cargo >/dev/null 2>&1; then + return + fi + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal +} + +save_sysctls() { + mkdir -p "$STATE_DIR" + [ -f "$STATE_DIR/sysctl.before" ] && return + : > "$STATE_DIR/sysctl.before" + for key in net.core.somaxconn net.ipv4.tcp_max_syn_backlog \ + net.ipv4.ip_local_port_range net.ipv4.tcp_tw_reuse fs.file-max \ + kernel.perf_event_paranoid; do + printf '%s=%s\n' "$key" "$(sysctl -n "$key")" >> "$STATE_DIR/sysctl.before" + done +} + +apply_tuning() { + sysctl -w net.core.somaxconn=65535 + sysctl -w net.ipv4.tcp_max_syn_backlog=65535 + sysctl -w net.ipv4.ip_local_port_range='1024 65535' + sysctl -w net.ipv4.tcp_tw_reuse=1 + sysctl -w fs.file-max=2097152 + sysctl -w kernel.perf_event_paranoid=-1 +} + +set_governor() { + for governor in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do + [ -f "$governor" ] || continue + printf '%s\n' performance > "$governor" || true + done +} + +install_packages +install_rust +save_sysctls +apply_tuning +set_governor +systemctl enable --now docker 2>/dev/null || true +echo "Host setup complete. Re-login after adding your benchmark user to the docker group." diff --git a/scripts/validate_environment.sh b/scripts/validate_environment.sh new file mode 100755 index 0000000..e7d4cc0 --- /dev/null +++ b/scripts/validate_environment.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env sh +set -eu + +TARGET_HOST="${BENCHMARK_TARGET_HOST:-127.0.0.1}" +RUNTIME="${CONTAINER_RUNTIME:-}" +FAILED=0 + +warn() { + echo "WARN: $*" >&2 + FAILED=1 +} + +find_runtime() { + if [ -n "$RUNTIME" ]; then + return + fi + if command -v docker >/dev/null 2>&1; then + RUNTIME=docker + return + fi + if command -v podman >/dev/null 2>&1; then + RUNTIME=podman + return + fi + warn "Docker or Podman is required." +} + +find_runtime +if [ -n "$RUNTIME" ] && ! "$RUNTIME" info >/dev/null 2>&1; then + warn "Cannot communicate with $RUNTIME." +fi + +if command -v ip >/dev/null 2>&1; then + route=$(ip route get "$TARGET_HOST" 2>/dev/null || true) + interface=$(printf '%s\n' "$route" | awk '/dev/ { for (i = 1; i <= NF; i++) if ($i == "dev") print $(i + 1) }') + [ -n "$interface" ] && ip link show "$interface" | grep -q 'mtu ' || warn "No route or MTU for $TARGET_HOST." +else + warn "iproute2 is required to inspect network settings." +fi + +if command -v ping >/dev/null 2>&1; then + ping -c 2 -W 1 "$TARGET_HOST" >/dev/null 2>&1 || warn "Cannot ping $TARGET_HOST." +fi + +memory_kb=$(awk '/MemAvailable:/ { print $2 }' /proc/meminfo 2>/dev/null || true) +[ "${memory_kb:-0}" -ge 1048576 ] || warn "Less than 1 GiB of available memory." +command -v nproc >/dev/null 2>&1 && echo "CPUs available: $(nproc)" +grep -q cgroup2 /proc/mounts || warn "cgroups v2 is not mounted." + +if command -v perf >/dev/null 2>&1; then + perf stat -e cycles true >/dev/null 2>&1 || warn "Hardware perf counters are unavailable." +else + warn "perf is not installed." +fi + +if [ "$FAILED" -ne 0 ]; then + echo "Environment validation found issues." >&2 + exit 1 +fi + +echo "Environment validation passed for $TARGET_HOST using $RUNTIME."