Merge branch 'async_http'

This commit is contained in:
2026-02-25 16:39:16 -06:00
3 changed files with 112 additions and 19 deletions

108
http.cm
View File

@@ -1,5 +1,6 @@
var socket = use('socket')
var tls = use('net/tls')
var Blob = use('blob')
def CRLF = "\r\n"
@@ -625,20 +626,106 @@ var receive_response = function(callback, state) {
}
// ============================================================
// fetch — composed requestor pipeline
// fetch — synchronous HTTP(S) GET, returns response body (stoned blob)
// ============================================================
var fetch = function(callback, value) {
def pipeline = runtime_env.sequence([
parse_url,
resolve_dns,
open_connection,
send_request,
receive_response
])
return pipeline(callback, value)
var fetch = function(url) {
var scheme = "http"
var rest = url
var scheme_end = search(url, "://")
var slash = null
var host_port = null
var path = "/"
var hp = null
var host = null
var port = null
var fd = null
var ctx = null
var buf = Blob()
var raw_text = null
var hdr_end = null
var header_text = null
var body_start_bits = null
var body = null
var addrs = null
var address = null
var ok = true
if (scheme_end != null) {
scheme = lower(text(url, 0, scheme_end))
rest = text(url, scheme_end + 3, length(url))
}
slash = search(rest, "/")
host_port = rest
if (slash != null) {
host_port = text(rest, 0, slash)
path = text(rest, slash, length(rest))
}
hp = array(host_port, ":")
host = hp[0]
port = length(hp) > 1 ? number(hp[1]) : (scheme == "https" ? 443 : 80)
addrs = socket.getaddrinfo(host, text(port))
if (addrs == null || length(addrs) == 0) return null
address = addrs[0].address
fd = socket.socket("AF_INET", "SOCK_STREAM")
var _do = function() {
var req = null
var chunk = null
socket.connect(fd, {address: address, port: port})
if (scheme == "https") ctx = tls.wrap(fd, host)
req = "GET " + path + " HTTP/1.1" + CRLF
req = req + "Host: " + host_port + CRLF
req = req + "Connection: close" + CRLF
req = req + "User-Agent: cell/1.0" + CRLF
req = req + "Accept: */*" + CRLF + CRLF
if (ctx != null) tls.send(ctx, req)
else socket.send(fd, req)
while (true) {
if (ctx != null) chunk = tls.recv(ctx, 16384)
else chunk = socket.recv(fd, 16384)
if (chunk == null) break
stone(chunk)
if (length(chunk) == 0) break
buf.write_blob(chunk)
}
} disruption {
ok = false
}
_do()
var _cleanup = function() {
if (ctx != null) tls.close(ctx)
else socket.close(fd)
} disruption {}
_cleanup()
if (!ok) return null
stone(buf)
raw_text = text(buf)
hdr_end = search(raw_text, CRLF + CRLF)
if (hdr_end == null) return null
header_text = text(raw_text, 0, hdr_end)
if (search(lower(header_text), "transfer-encoding: chunked") != null)
return decode_chunked(text(raw_text, hdr_end + 4))
// Headers are ASCII so char offset = byte offset
body_start_bits = (hdr_end + 4) * 8
body = buf.read_blob(body_start_bits, length(buf))
stone(body)
return body
}
// ============================================================
// fetch_requestor — async requestor pipeline for fetch
// ============================================================
var fetch_requestor = sequence([
parse_url,
resolve_dns,
open_connection,
send_request,
receive_response
])
function close(fd) {
socket.close(fd)
}
@@ -650,5 +737,6 @@ return {
sse_open: sse_open, sse_event: sse_event, sse_close: sse_close,
// client
fetch: fetch,
fetch_requestor: fetch_requestor,
request: request
}

View File

@@ -822,9 +822,8 @@ var $_ = {}
use_cache['core/json'] = json
// Create runtime_env early (empty) -- filled after pronto loads.
// Shop accesses it lazily (in inject_env, called at module-use time, not load time)
// so it sees the filled version.
// Runtime env: passed to package modules via shop's inject_env.
// Requestor functions are added immediately below; actor/log/send added later.
var runtime_env = {}
// Populate core_extras with everything shop (and other core modules) need
@@ -848,6 +847,18 @@ core_extras.ensure_build_dir = ensure_build_dir
core_extras.compile_to_blob = compile_to_blob
core_extras.native_mode = native_mode
// Load pronto early so requestor functions (sequence, parallel, etc.) are
// available to core modules loaded below (http, shop, etc.)
var pronto = use_core('internal/pronto')
var fallback = pronto.fallback
var parallel = pronto.parallel
var race = pronto.race
var sequence = pronto.sequence
core_extras.fallback = fallback
core_extras.parallel = parallel
core_extras.race = race
core_extras.sequence = sequence
// NOW load shop -- it receives all of the above via env
var shop = use_core('internal/shop')
use_core('build')
@@ -1074,12 +1085,6 @@ actor_mod.set_log(log)
// (before the full log was ready) captured the bootstrap function reference.
_log_full = log
var pronto = use_core('pronto')
var fallback = pronto.fallback
var parallel = pronto.parallel
var race = pronto.race
var sequence = pronto.sequence
runtime_env.actor = actor
runtime_env.log = log
runtime_env.send = send