hew paste
// 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<string, string> {
        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
    }
}