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 new file mode 100644 index 00000000..78a47465 --- /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 = "0.3" +httparse = "1" +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..b8481a54 --- /dev/null +++ b/frameworks/may-minihttp/src/main.rs @@ -0,0 +1,256 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +use may::net::TcpListener; +use std::io::{self, Read, Write}; + +const BUF_SIZE: usize = 4096; +const MAX_HEADERS: usize = 16; + +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 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 +} + +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); + 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; + } + } + } + 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; + } + + if filled == 0 { + break; + } + } + } +} + +fn main() { + 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); + }); + } + } +}