//! `httpd [-p port] [-n count] [-t seconds]`: a tiny web server. //! //! The server side of sockets: `bind` to a port, `listen`, then `accept` //! connections one by one. Each request gets one answer and the connection //! is closed (HTTP/1.0 style). `/` is a greeting page; any other path is a //! file from the current directory (`cd B:` then `httpd` serves the FAT32 //! disk). //! //! QEMU's user-mode network hides the guest behind NAT, so to reach this //! from the host QEMU must forward a port, e.g. //! -netdev user,id=net0,hostfwd=tcp:127.0.0.1:5580-:80 //! and then `curl http://127.0.0.1:5580/` on the host. //! //! `-n` stops after that many requests; `-t` gives up if no request comes //! within that many seconds (both used by the self-test). #![no_std] #![no_main] #[macro_use] #[path = "ulib.rs"] mod ulib; extern crate alloc; use alloc::format; fn main() -> i64 { let (mut port, mut max_requests, mut timeout_s) = (80u16, u64::MAX, 0u64); let mut i = 1; while let Some(a) = ulib::arg(i) { let value = ulib::arg(i + 1).and_then(|v| v.parse::().ok()); match (a, value) { ("-p", Some(v)) => port = v as u16, ("-n", Some(v)) => max_requests = v, ("-t", Some(v)) => timeout_s = v, _ => { eprintln!("usage: httpd [-p port] [-n count] [-t seconds]"); return 2; } } i += 2; } let fd = ulib::socket(ulib::SOCK_STREAM, 0); for r in [fd, ulib::bind(fd, port), ulib::listen(fd, 4)] { if r < 0 { eprintln!("httpd: {}", ulib::strerror(r)); return 1; } } ulib::set_recv_timeout(fd, timeout_s * 1000); // 0 = wait forever println!("[httpd] listening on port {}", port); let mut served = 0; while served < max_requests { let (conn, peer) = ulib::accept(fd); if conn < 0 { println!("[httpd] no request within {} s, giving up", timeout_s); return 1; } served += 1; handle(conn, peer, served); ulib::close(conn); } println!("[httpd] served {} request{}, bye", served, if served == 1 { "" } else { "s" }); 0 } /// Read one request, send one response. fn handle(conn: i64, peer: ulib::SockAddrIn, visitor: u64) { ulib::set_recv_timeout(conn, 5000); let mut req = [0u8; 2048]; let mut len = 0; while len < req.len() && !req[..len].windows(4).any(|w| w == b"\r\n\r\n") { let n = ulib::read(conn, &mut req[len..]); if n <= 0 { return; // gave up or hung up } len += n as usize; } let line = req[..len].split(|&b| b == b'\r').next().unwrap_or(b""); let line = core::str::from_utf8(line).unwrap_or(""); let mut words = line.split(' '); let (method, path) = (words.next().unwrap_or(""), words.next().unwrap_or("/")); let (status, bytes) = if method != "GET" { respond(conn, "405 Method Not Allowed", b"only GET, sorry\n") } else if path == "/" { let mut uts = [0u8; 6 * 65]; let u = ulib::uname(&mut uts); let body = format!( "Hello from Rustbelt! {} {} ({})\nYou are visitor {}. Ask for /path to get a file from my current directory.\n", u[0], u[2], u[3], visitor ); respond(conn, "200 OK", body.as_bytes()) } else if path.contains("..") { respond(conn, "403 Forbidden", b"no ..\n") } else { send_file(conn, &path[1..]) }; println!("[httpd] {}:{} {} {} -> {} ({} bytes)", peer.ip(), peer.port(), method, path, status, bytes); } fn respond(conn: i64, status: &'static str, body: &[u8]) -> (&'static str, usize) { let head = format!("HTTP/1.0 {}\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nServer: rustbelt-httpd\r\n\r\n", status, body.len()); ulib::write_all(conn, head.as_bytes()); ulib::write_all(conn, body); (&status[..3], body.len()) } fn send_file(conn: i64, path: &str) -> (&'static str, usize) { let mut st = ulib::Stat::default(); if ulib::stat(path, &mut st) < 0 || st.kind != ulib::KIND_FILE { return respond(conn, "404 Not Found", b"not found\n"); } let fd = ulib::open(path, 0); if fd < 0 { return respond(conn, "404 Not Found", b"not found\n"); } let head = format!("HTTP/1.0 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: {}\r\n\r\n", st.size); ulib::write_all(conn, head.as_bytes()); let mut buf = [0u8; 4096]; let mut sent = 0; loop { let n = ulib::read(fd, &mut buf); if n <= 0 || ulib::write_all(conn, &buf[..n as usize]) < 0 { break; } sent += n as usize; } ulib::close(fd); ("200", sent) }