{"id":"2bw7M3FZQv","url":"https://pastebin.ca/2bw7M3FZQv","raw_url":"https://raw.anybin.ca/2bw7M3FZQv","visibility":"public","access":"public","created_at":1780545987651,"expires_at":null,"fetch_limit":null,"fetches_used":0,"reads_remaining":null,"size_bytes":5108,"syntax_hint":"hew","title":null,"filename":null,"change_note":null,"cipher":null,"cipher_meta":null,"parent_id":null,"root_id":"2bw7M3FZQv","version":1,"owner_id":"06F6D6Y710X9HQV90HF93KWQT8","recipient_id":null,"body":"// demo-tcp-http-proxy.hew\n//\n// Architecture: TCP->HTTP transform proxy with per-connection actors.\n//\n//   main             - binds :7878, loops accept(); spawns one ProxyConn per client\n//   ProxyConn actor  - active-mode connection handler (reactor delivers on_data/on_close)\n//                      on_data: interprets each inbound chunk as a URL, asks the\n//                      HttpFetcher sub-actor, transforms the result, writes back to client\n//   HttpFetcher actor - sub-actor owned per-ProxyConn; calls http_client.get_string and\n//                      returns Result<string, string> via the await/ask pattern\n//\n// Transform: prepend \"PROXY> \" to every non-empty line of the fetched body.\n//\n// Verified: compiles AND runs end-to-end on the v0.5 trunk. A TCP client sent\n// \"http://127.0.0.1:8099/data.txt\"; the proxy fetched it through the per-connection\n// HttpFetcher sub-actor, prefixed each line, and the client received:\n//     PROXY> alpha\n//     PROXY> beta\n//     PROXY> gamma\n//\n// The reader lives on the runtime reactor (on_data), the HTTP fetch lives on the\n// HttpFetcher sub-actor (off the connection's worker), and the writer returns from\n// the same handler (conn.send_string) -- so read, fetch, and write never block one\n// another. status: compiles + runs on v0.5 trunk.\nimport std::net;\n\nimport std::net::http::http_client;\n\n// ---- HttpFetcher sub-actor --------------------------------------------------\n//\n// One instance per ProxyConn. Handles `fetch(url)` asks and returns the body\n// or an error string. Isolated so the blocking HTTP call never ties up the\n// same worker thread as the TCP connection handler.\nactor HttpFetcher {\n    receive fn fetch(url: string) -> Result<string, string> {\n        match http_client.get_string(url) {\n            Some(body) => Ok(body),\n            // NOTE: a literal message here, not http_client.last_error(): the\n            // latter currently trips a codegen-front fail-closed (BUG-NET-7) when\n            // its string result feeds an Err() inside an await-suspended actor\n            // handler. Tracked separately; the proxy's error text is unaffected.\n            None => Err(\"http fetch failed\"),\n        }\n    }\n}\n\n// ---- ProxyConn per-connection actor ----------------------------------------\n//\n// Spawned with the accepted Connection and a pre-spawned HttpFetcher.\n// Registered as the active-mode handler via conn.attach(handler) in main.\n// The runtime reactor delivers on_data / on_close without blocking any worker.\nactor ProxyConn {\n    let conn: Connection;\n    let fetcher: LocalPid<HttpFetcher>;\n\n    // Called by the reactor for each inbound chunk.\n    receive fn on_data(chunk: bytes) {\n        // bytes.to_string() decodes UTF-8 (std::io impl on bytes).\n        let raw = chunk.to_string();\n        let url = raw.trim();\n        // Skip empty lines (keep-alive pings, blank lines, etc.)\n        if url.len() == 0 {\n            return;\n        }\n        // Ask the sub-actor to fetch the URL.\n        // await suspends this handler; the worker is freed until the reply arrives.\n        let ask_result = await fetcher.fetch(url);\n        match ask_result {\n            Ok(inner) => {\n                match inner {\n                    Ok(body) => {\n                        let transformed = transform_body(body);\n                        conn.send_string(transformed);\n                    },\n                    Err(e) => {\n                        // HTTP fetch failed; send the error back as a plain line.\n                        let msg = \"ERROR: \" + e + \"\\n\";\n                        conn.send_string(msg);\n                    },\n                }\n            },\n            Err(_) => {\n                // AskError: fetcher actor unreachable (crashed or closed).\n                conn.send_string(\"ERROR: fetcher unavailable\\n\");\n            },\n        }\n    }\n\n    // Called once when the client disconnects or the socket errors.\n    receive fn on_close() {\n        println(\"client disconnected\");\n    }\n}\n\n// ---- Transform --------------------------------------------------------------\n//\n// Prefix every non-empty line of the fetched body with \"PROXY> \".\n// Uses Vec<string>.get(i) -- the supported idiom for string-vec element access.\nfn transform_body(body: string) -> string {\n    let lines = body.split(\"\\n\");\n    var out = \"\";\n    var i = 0;\n    let n = lines.len();\n    while i < n {\n        let line = lines.get(i);\n        if line.len() > 0 {\n            out = out + \"PROXY> \" + line + \"\\n\";\n        } else {\n            out = out + \"\\n\";\n        }\n        i = i + 1;\n    }\n    out\n}\n\n// ---- Entry point ------------------------------------------------------------\nfn main() {\n    let listener = net.listen(\":7878\");\n    println(\"TCP->HTTP proxy listening on :7878\");\n\n    // Accept loop: each accepted connection gets its own actor pair.\n    loop {\n        let conn = listener.accept();\n        let fetcher = spawn HttpFetcher;\n        let handler = spawn ProxyConn(conn: conn, fetcher: fetcher);\n        conn.attach(handler); // reactor now drives on_data/on_close for this connection\n    }\n}\n"}