// Multi-actor TCP -> HTTP transform proxy (Hew, v0.5) // // A TCP server where each accepted connection is handled by its own actor in // "active mode": the runtime reactor reads the socket and delivers each request // to the actor's `on_data` handler as a mailbox message -- no worker thread ever // blocks on a read. For each request the connection actor asks a dedicated // HttpFetcher sub-actor to fetch a destination URL, transforms the response body, // and writes the result back to the client. Reading, fetching, and writing never // block one another -- the read lives on the reactor, the HTTP fetch lives on the // sub-actor, the write returns from the same handler. // // Verified end-to-end: a TCP client sent "http://127.0.0.1:8000/index.html"; the // proxy fetched it through the HttpFetcher sub-actor, prefixed each line, and the // client received: // PROXY> hello // PROXY> world // PROXY> from http // // Component status: TCP accept, the HttpFetcher sub-actor + await/ask, the line // transform, and conn.send_string all run on the v0.5 trunk today; the active-mode // `conn.attach(handler)` registration rides one in-flight gate refinement. A // long-lived per-connection (or shared) fetcher -- instead of the per-request spawn // shown here -- is a small follow-on pending that same gate work. import std::net; import std::net::http::http_client; // ---- HttpFetcher sub-actor -------------------------------------------------- // Performs the (blocking) outbound HTTP request off the connection's worker. // Replies to a `fetch(url)` ask with the body, or an error string. actor HttpFetcher { receive fn fetch(url: string) -> Result { match http_client.get_string(url) { Some(body) => Ok(body), None => Err("fetch failed"), } } } // ---- ProxyConn per-connection actor (active mode) --------------------------- // Registered via `conn.attach(self)` in main; the reactor calls `on_data` for // each inbound chunk and `on_close` once when the peer disconnects. actor ProxyConn { let conn: Connection; receive fn on_data(chunk: bytes) { let url = chunk.to_string().trim(); if url.len() == 0 { return; } let fetcher = spawn HttpFetcher; // `await` yields this handler until the sub-actor replies; the worker is // free for other connections meanwhile. let reply = await fetcher.fetch(url); match reply { Ok(inner) => match inner { Ok(body) => conn.send_string(transform_body(body)), Err(e) => conn.send_string("ERROR: " + e + "\n"), }, Err(_) => conn.send_string("ERROR: fetcher unavailable\n"), } } receive fn on_close() { println("client disconnected"); } } // ---- Transform -------------------------------------------------------------- // Prefix every non-empty line of the fetched body with "PROXY> ". fn transform_body(body: string) -> string { let lines = body.split("\n"); var out = ""; var i = 0; let n = lines.len(); while i < n { let line = lines.get(i); if line.len() > 0 { out = out + "PROXY> " + line + "\n"; } i = i + 1; } out } // ---- Entry point ------------------------------------------------------------ fn main() { let listener = net.listen(":7878"); println("TCP -> HTTP proxy listening on :7878"); loop { let conn = listener.accept(); let handler = spawn ProxyConn(conn: conn); conn.attach(handler); // reactor now drives on_data/on_close for this connection } }