From bf31bd09b51960524b81029ea54be228e8714c5b Mon Sep 17 00:00:00 2001 From: BennyFranciscus <268274351+BennyFranciscus@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:04:08 +0000 Subject: [PATCH 1/2] Add may-minihttp (Rust stackful coroutines engine) may-minihttp is a mini HTTP server built on the May coroutine library, which uses stackful coroutines for cooperative scheduling. It follows a coroutine-per-connection model similar to goroutines but in Rust. Implemented as an engine with baseline, pipelined, and limited-conn test profiles. Uses mimalloc allocator and thin LTO for performance. Repo: https://github.com/Xudong-Huang/may_minihttp --- frameworks/may-minihttp/Cargo.toml | 16 +++++++ frameworks/may-minihttp/Dockerfile | 11 +++++ frameworks/may-minihttp/meta.json | 14 ++++++ frameworks/may-minihttp/src/main.rs | 71 +++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 frameworks/may-minihttp/Cargo.toml create mode 100644 frameworks/may-minihttp/Dockerfile create mode 100644 frameworks/may-minihttp/meta.json create mode 100644 frameworks/may-minihttp/src/main.rs diff --git a/frameworks/may-minihttp/Cargo.toml b/frameworks/may-minihttp/Cargo.toml new file mode 100644 index 00000000..d242160e --- /dev/null +++ b/frameworks/may-minihttp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "httparena-may-minihttp" +version = "0.1.0" +edition = "2021" + +[dependencies] +may_minihttp = "0.1" +may = "0.3" +num_cpus = "1" +mimalloc = { version = "0.1", default-features = false } + +[profile.release] +opt-level = 3 +codegen-units = 1 +lto = "thin" +panic = "abort" diff --git a/frameworks/may-minihttp/Dockerfile b/frameworks/may-minihttp/Dockerfile new file mode 100644 index 00000000..b557ba66 --- /dev/null +++ b/frameworks/may-minihttp/Dockerfile @@ -0,0 +1,11 @@ +FROM rust:1.88 AS build +WORKDIR /app +COPY Cargo.toml . +RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src/ target/release/httparena-may-minihttp* target/release/deps/httparena_may_minihttp* +COPY src ./src +RUN RUSTFLAGS="-C target-cpu=native" cargo build --release + +FROM debian:bookworm-slim +COPY --from=build /app/target/release/httparena-may-minihttp /server +EXPOSE 8080 +CMD ["/server"] diff --git a/frameworks/may-minihttp/meta.json b/frameworks/may-minihttp/meta.json new file mode 100644 index 00000000..bf671ac2 --- /dev/null +++ b/frameworks/may-minihttp/meta.json @@ -0,0 +1,14 @@ +{ + "display_name": "may-minihttp", + "language": "Rust", + "type": "engine", + "engine": "may-minihttp", + "description": "Mini HTTP server built on May stackful coroutines. Uses cooperative scheduling with coroutine-per-connection model for high concurrency.", + "repo": "https://github.com/Xudong-Huang/may_minihttp", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn" + ] +} diff --git a/frameworks/may-minihttp/src/main.rs b/frameworks/may-minihttp/src/main.rs new file mode 100644 index 00000000..10f0bc6d --- /dev/null +++ b/frameworks/may-minihttp/src/main.rs @@ -0,0 +1,71 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +use may_minihttp::{HttpServer, HttpService, Request, Response}; +use std::io::{self, Read}; + +#[derive(Clone)] +struct Server; + +fn parse_query_params(path: &str) -> i64 { + let query = match path.find('?') { + Some(pos) => &path[pos + 1..], + None => return 0, + }; + let mut sum: i64 = 0; + for pair in query.split('&') { + if let Some(val) = pair.split('=').nth(1) { + if let Ok(n) = val.parse::() { + sum += n; + } + } + } + sum +} + +impl HttpService for Server { + fn call(&mut self, req: Request, rsp: &mut Response) -> io::Result<()> { + let path = req.path(); + let route = match path.find('?') { + Some(pos) => &path[..pos], + None => path, + }; + + match route { + "/baseline11" => { + rsp.header("Content-Type: text/plain"); + let method = req.method(); + if method == "POST" { + let mut sum = parse_query_params(path); + let mut body = req.body(); + let mut buf = Vec::new(); + body.read_to_end(&mut buf)?; + if let Ok(s) = std::str::from_utf8(&buf) { + if let Ok(n) = s.trim().parse::() { + sum += n; + } + } + rsp.body_vec(sum.to_string().into_bytes()); + } else { + let sum = parse_query_params(path); + rsp.body_vec(sum.to_string().into_bytes()); + } + } + "/pipeline" => { + rsp.header("Content-Type: text/plain"); + rsp.body("ok"); + } + _ => { + rsp.status_code(404, "Not Found"); + rsp.body("not found"); + } + } + Ok(()) + } +} + +fn main() { + may::config().set_workers(num_cpus::get()); + let server = HttpServer(Server).start("0.0.0.0:8080").unwrap(); + server.wait(); +} From 36f6bab4f6e156388d2365e0b77e5597d7409b5e Mon Sep 17 00:00:00 2001 From: BennyFranciscus <268274351+BennyFranciscus@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:26:49 +0000 Subject: [PATCH 2/2] fix: rewrite to raw may + httparse for chunked TE support may_minihttp's BodyReader only supports Content-Length bodies. Chunked Transfer-Encoding requests crash the connection parser since the chunk framing corrupts subsequent request parsing. Rewritten to use may TCP directly with httparse for header parsing and manual chunked TE decoding. This fixes the CI validate failure on the chunked POST baseline test. --- frameworks/may-minihttp/Cargo.lock | 437 ++++++++++++++++++++++++++++ frameworks/may-minihttp/Cargo.toml | 2 +- frameworks/may-minihttp/src/main.rs | 273 ++++++++++++++--- 3 files changed, 667 insertions(+), 45 deletions(-) create mode 100644 frameworks/may-minihttp/Cargo.lock diff --git a/frameworks/may-minihttp/Cargo.lock b/frameworks/may-minihttp/Cargo.lock new file mode 100644 index 00000000..f531c92e --- /dev/null +++ b/frameworks/may-minihttp/Cargo.lock @@ -0,0 +1,437 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[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 = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "httparena-may-minihttp" +version = "0.1.0" +dependencies = [ + "httparse", + "may", + "mimalloc", + "num_cpus", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +dependencies = [ + "cc", + "libc", +] + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "may" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a812e25193d07288695d0e12e30ec5cd52f9fefc3c55038b89f1ef906dcab078" +dependencies = [ + "cfg-if", + "core_affinity", + "crossbeam", + "generator", + "libc", + "log", + "may_queue", + "nix", + "num_cpus", + "parking_lot", + "rustversion", + "smallvec", + "socket2", + "windows-sys 0.59.0", +] + +[[package]] +name = "may_queue" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7026ba39000f40c16ba8ea206967380471726dd26fc34f51491b47fba9d84a94" +dependencies = [ + "crossbeam-utils", + "rustversion", + "smallvec", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mimalloc" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[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 = "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 = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[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 = "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-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[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" diff --git a/frameworks/may-minihttp/Cargo.toml b/frameworks/may-minihttp/Cargo.toml index d242160e..78a47465 100644 --- a/frameworks/may-minihttp/Cargo.toml +++ b/frameworks/may-minihttp/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -may_minihttp = "0.1" may = "0.3" +httparse = "1" num_cpus = "1" mimalloc = { version = "0.1", default-features = false } diff --git a/frameworks/may-minihttp/src/main.rs b/frameworks/may-minihttp/src/main.rs index 10f0bc6d..b8481a54 100644 --- a/frameworks/may-minihttp/src/main.rs +++ b/frameworks/may-minihttp/src/main.rs @@ -1,71 +1,256 @@ #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -use may_minihttp::{HttpServer, HttpService, Request, Response}; -use std::io::{self, Read}; +use may::net::TcpListener; +use std::io::{self, Read, Write}; -#[derive(Clone)] -struct Server; +const BUF_SIZE: usize = 4096; +const MAX_HEADERS: usize = 16; -fn parse_query_params(path: &str) -> i64 { - let query = match path.find('?') { +fn parse_query_params(path: &[u8]) -> i64 { + let qs = match memchr(b'?', path) { Some(pos) => &path[pos + 1..], None => return 0, }; let mut sum: i64 = 0; - for pair in query.split('&') { - if let Some(val) = pair.split('=').nth(1) { - if let Ok(n) = val.parse::() { - sum += n; + for pair in qs.split(|&b| b == b'&') { + if let Some(pos) = memchr(b'=', pair) { + if let Ok(s) = std::str::from_utf8(&pair[pos + 1..]) { + if let Ok(n) = s.parse::() { + sum += n; + } } } } sum } -impl HttpService for Server { - fn call(&mut self, req: Request, rsp: &mut Response) -> io::Result<()> { - let path = req.path(); - let route = match path.find('?') { - Some(pos) => &path[..pos], - None => path, - }; - - match route { - "/baseline11" => { - rsp.header("Content-Type: text/plain"); - let method = req.method(); - if method == "POST" { +fn memchr(needle: u8, haystack: &[u8]) -> Option { + haystack.iter().position(|&b| b == needle) +} + +fn route_path(path: &[u8]) -> &[u8] { + match memchr(b'?', path) { + Some(pos) => &path[..pos], + None => path, + } +} + +/// Read body with Content-Length +fn read_body_content_length( + buf: &[u8], + stream: &mut may::net::TcpStream, + content_length: usize, +) -> io::Result> { + let mut body = Vec::with_capacity(content_length); + let take = content_length.min(buf.len()); + body.extend_from_slice(&buf[..take]); + let mut remaining = content_length - take; + while remaining > 0 { + let mut tmp = [0u8; 4096]; + let n = stream.read(&mut tmp)?; + if n == 0 { + break; + } + let take = remaining.min(n); + body.extend_from_slice(&tmp[..take]); + remaining -= take; + } + Ok(body) +} + +/// Read chunked transfer-encoded body +fn read_body_chunked( + buf: &[u8], + stream: &mut may::net::TcpStream, +) -> io::Result> { + let mut body = Vec::new(); + let mut data = buf.to_vec(); + + loop { + // Find chunk size line + loop { + if let Some(pos) = find_crlf(&data) { + let size_str = std::str::from_utf8(&data[..pos]).unwrap_or("0").trim(); + let chunk_size = usize::from_str_radix(size_str, 16).unwrap_or(0); + data.drain(..pos + 2); // consume size line + CRLF + + if chunk_size == 0 { + // Terminal chunk — consume trailing CRLF + return Ok(body); + } + + // Read chunk_size bytes + trailing CRLF + while data.len() < chunk_size + 2 { + let mut tmp = [0u8; 4096]; + let n = stream.read(&mut tmp)?; + if n == 0 { + return Ok(body); + } + data.extend_from_slice(&tmp[..n]); + } + body.extend_from_slice(&data[..chunk_size]); + data.drain(..chunk_size + 2); // consume chunk data + CRLF + break; // parse next chunk + } + + // Need more data for chunk size line + let mut tmp = [0u8; 4096]; + let n = stream.read(&mut tmp)?; + if n == 0 { + return Ok(body); + } + data.extend_from_slice(&tmp[..n]); + } + } +} + +fn find_crlf(data: &[u8]) -> Option { + for i in 0..data.len().saturating_sub(1) { + if data[i] == b'\r' && data[i + 1] == b'\n' { + return Some(i); + } + } + None +} + +fn handle_connection(mut stream: may::net::TcpStream) -> io::Result<()> { + let mut buf = vec![0u8; BUF_SIZE]; + let mut filled = 0; + + loop { + // Read more data + if filled == buf.len() { + buf.resize(buf.len() * 2, 0); + } + let n = stream.read(&mut buf[filled..])?; + if n == 0 { + return Ok(()); + } + filled += n; + + // Try to parse request + loop { + let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; + let mut req = httparse::Request::new(&mut headers); + let status = match req.parse(&buf[..filled]) { + Ok(s) => s, + Err(_) => return Ok(()), + }; + + let header_len = match status { + httparse::Status::Complete(len) => len, + httparse::Status::Partial => break, // need more data + }; + + let method = req.method.unwrap_or("GET"); + let path = req.path.unwrap_or("/").as_bytes(); + let route = route_path(path); + + // Find content-length and transfer-encoding + let mut content_length: Option = None; + let mut is_chunked = false; + for h in req.headers.iter() { + if h.name.eq_ignore_ascii_case("content-length") { + content_length = std::str::from_utf8(h.value) + .ok() + .and_then(|s| s.trim().parse().ok()); + } else if h.name.eq_ignore_ascii_case("transfer-encoding") { + if let Ok(v) = std::str::from_utf8(h.value) { + is_chunked = v.to_ascii_lowercase().contains("chunked"); + } + } + } + + // Body data starts after headers + let body_start = &buf[header_len..filled]; + + // Read body if POST + let body_bytes: Option> = if method == "POST" { + if is_chunked { + Some(read_body_chunked(body_start, &mut stream)?) + } else if let Some(cl) = content_length { + let body = read_body_content_length(body_start, &mut stream, cl)?; + Some(body) + } else { + None + } + } else { + None + }; + + // Compute how much of buf was consumed (headers + body from buf) + let body_consumed = if method == "POST" { + if is_chunked { + // chunked reader consumed from body_start + stream + body_start.len() + } else if let Some(cl) = content_length { + cl.min(body_start.len()) + } else { + 0 + } + } else { + 0 + }; + let total_consumed = header_len + body_consumed; + + // Route and generate response + let response = match route { + b"/baseline11" => { let mut sum = parse_query_params(path); - let mut body = req.body(); - let mut buf = Vec::new(); - body.read_to_end(&mut buf)?; - if let Ok(s) = std::str::from_utf8(&buf) { - if let Ok(n) = s.trim().parse::() { - sum += n; + if let Some(ref body) = body_bytes { + if let Ok(s) = std::str::from_utf8(body) { + if let Ok(n) = s.trim().parse::() { + sum += n; + } } } - rsp.body_vec(sum.to_string().into_bytes()); - } else { - let sum = parse_query_params(path); - rsp.body_vec(sum.to_string().into_bytes()); + let body_str = sum.to_string(); + format!( + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{}", + body_str.len(), + body_str + ) } + b"/pipeline" => { + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nok" + .to_string() + } + _ => { + "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\n\r\nnot found" + .to_string() + } + }; + + stream.write_all(response.as_bytes())?; + + // Shift remaining data + if total_consumed < filled { + buf.copy_within(total_consumed..filled, 0); + filled -= total_consumed; + } else { + filled = 0; } - "/pipeline" => { - rsp.header("Content-Type: text/plain"); - rsp.body("ok"); - } - _ => { - rsp.status_code(404, "Not Found"); - rsp.body("not found"); + + if filled == 0 { + break; } } - Ok(()) } } fn main() { - may::config().set_workers(num_cpus::get()); - let server = HttpServer(Server).start("0.0.0.0:8080").unwrap(); - server.wait(); + let cpus = num_cpus::get(); + may::config().set_workers(cpus); + + let listener = TcpListener::bind("0.0.0.0:8080").unwrap(); + eprintln!("may-minihttp listening on :8080 with {} workers", cpus); + + while let Ok((stream, _)) = listener.accept() { + unsafe { + may::coroutine::spawn(move || { + let _ = handle_connection(stream); + }); + } + } }