hew paste
// demo-tcp-http-proxy.hew
//
// Architecture: TCP->HTTP transform proxy with per-connection actors.
//
//   main             - binds :7878, loops accept(); spawns one ProxyConn per client
//   ProxyConn actor  - active-mode connection handler (reactor delivers on_data/on_close)
//                      on_data: interprets each inbound chunk as a URL, asks the
//                      HttpFetcher sub-actor, transforms the result, writes back to client
//   HttpFetcher actor - sub-actor owned per-ProxyConn; calls http_client.get_string and
//                      returns Result<string, string> via the await/ask pattern
//
// Transform: prepend "PROXY> " to every non-empty line of the fetched body.
//
// Verified: compiles AND runs end-to-end on the v0.5 trunk. A TCP client sent
// "http://127.0.0.1:8099/data.txt"; the proxy fetched it through the per-connection
// HttpFetcher sub-actor, prefixed each line, and the client received:
//     PROXY> alpha
//     PROXY> beta
//     PROXY> gamma
//
// The reader lives on the runtime reactor (on_data), the HTTP fetch lives on the
// HttpFetcher sub-actor (off the connection's worker), and the writer returns from
// the same handler (conn.send_string) -- so read, fetch, and write never block one
// another. status: compiles + runs on v0.5 trunk.
import std::net;

import std::net::http::http_client;

// ---- HttpFetcher sub-actor --------------------------------------------------
//
// One instance per ProxyConn. Handles `fetch(url)` asks and returns the body
// or an error string. Isolated so the blocking HTTP call never ties up the
// same worker thread as the TCP connection handler.
actor HttpFetcher {
    receive fn fetch(url: string) -> Result<string, string> {
        match http_client.get_string(url) {
            Some(body) => Ok(body),
            // NOTE: a literal message here, not http_client.last_error(): the
            // latter currently trips a codegen-front fail-closed (BUG-NET-7) when
            // its string result feeds an Err() inside an await-suspended actor
            // handler. Tracked separately; the proxy's error text is unaffected.
            None => Err("http fetch failed"),
        }
    }
}

// ---- ProxyConn per-connection actor ----------------------------------------
//
// Spawned with the accepted Connection and a pre-spawned HttpFetcher.
// Registered as the active-mode handler via conn.attach(handler) in main.
// The runtime reactor delivers on_data / on_close without blocking any worker.
actor ProxyConn {
    let conn: Connection;
    let fetcher: LocalPid<HttpFetcher>;

    // Called by the reactor for each inbound chunk.
    receive fn on_data(chunk: bytes) {
        // bytes.to_string() decodes UTF-8 (std::io impl on bytes).
        let raw = chunk.to_string();
        let url = raw.trim();
        // Skip empty lines (keep-alive pings, blank lines, etc.)
        if url.len() == 0 {
            return;
        }
        // Ask the sub-actor to fetch the URL.
        // await suspends this handler; the worker is freed until the reply arrives.
        let ask_result = await fetcher.fetch(url);
        match ask_result {
            Ok(inner) => {
                match inner {
                    Ok(body) => {
                        let transformed = transform_body(body);
                        conn.send_string(transformed);
                    },
                    Err(e) => {
                        // HTTP fetch failed; send the error back as a plain line.
                        let msg = "ERROR: " + e + "\n";
                        conn.send_string(msg);
                    },
                }
            },
            Err(_) => {
                // AskError: fetcher actor unreachable (crashed or closed).
                conn.send_string("ERROR: fetcher unavailable\n");
            },
        }
    }

    // Called once when the client disconnects or the socket errors.
    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 = "";
    for i in 0 .. lines.len() {
        let line = lines.get(i);
        if line.len() > 0 {
            out = out + "PROXY> " + line + "\n";
        } else {
            out = out + "\n";
        }
    }
    out
}

// ---- Entry point ------------------------------------------------------------
fn main() {
    let listener = net.listen(":7878");
    println("TCP->HTTP proxy listening on :7878");

    // Accept loop: each accepted connection gets its own actor pair.
    loop {
        let conn = listener.accept();
        let fetcher = spawn HttpFetcher;
        let handler = spawn ProxyConn(conn: conn, fetcher: fetcher);
        conn.attach(handler); // reactor now drives on_data/on_close for this connection
    }
}