use ::std::cell::RefCell;
use ::std::ptr::NonNull;
use ::std::sync::atomic::{AtomicU64, Ordering};
use bun_core::ZBox;
use mozjs::jsapi::*;
use mozjs::jsval::{Int32Value, JSVal, ObjectValue, StringValue, UndefinedValue};
use mozjs::realm::AutoRealm;
use mozjs::rooted;
use mozjs::rust::wrappers2 as w2;
use bun_uws_sys::app::App;
use bun_uws_sys::request::Request;
use bun_uws_sys::response::Response;
use bun_uws_sys::socket_context::BunSocketContextOptions;
use crate::gc_store::{gc_store_get_ns, gc_store_insert_ns, gc_store_remove_ns};
use crate::require::cache_builtin;
static NEXT_SERVER_ID: AtomicU64 = AtomicU64::new(1);
const HTTP_CLIENT_JS: &str = r#"(function(h){
function ClientRequest(opts, cb) {
throw new Error("require('http').ClientRequest is not an entry point in bao: constructing it directly would bypass the request pipeline. Use require('http').request() — the real network path — instead.");
}
function IncomingMessage(socket) {
throw new Error("require('http').IncomingMessage is not an entry point in bao: response objects are handed to you by require('http').request(). Use require('http').request() — the real network path — instead.");
}
function OutgoingMessage() {
throw new Error("require('http').OutgoingMessage is not an entry point in bao: use require('http').request() — the real network path — instead.");
}
h.ClientRequest = ClientRequest;
h.IncomingMessage = IncomingMessage;
h.OutgoingMessage = OutgoingMessage;
function attachEE(obj) {
obj._hh = {};
obj.on = function (ev, fn) {
(obj._hh[ev] || (obj._hh[ev] = [])).push(fn);
return obj;
};
obj.once = function (ev, fn) {
var wrap = function () { obj.off(ev, wrap); fn.apply(obj, arguments); };
(obj._hh[ev] || (obj._hh[ev] = [])).push(wrap);
return obj;
};
obj.addListener = obj.on;
obj.off = function (ev, fn) {
var ls = obj._hh[ev];
if (ls) { var i = ls.indexOf(fn); if (i >= 0) ls.splice(i, 1); }
return obj;
};
obj.removeListener = obj.off;
obj.removeAllListeners = function (ev) {
if (ev) { delete obj._hh[ev]; } else { obj._hh = {}; }
return obj;
};
obj.emit = function (ev) {
var ls = (obj._hh[ev] || []).slice();
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < ls.length; i++) ls[i].apply(obj, args);
return ls.length > 0;
};
obj.listenerCount = function (ev) { return (obj._hh[ev] || []).length; };
return obj;
}
// WHATWG Headers instance (web_fetch_classes) → Node's IncomingMessage
// headers shape: a plain object keyed by lower-cased names. Headers#get
// normalises case-insensitively and joins repeated values with ', ' —
// forEach walks exactly those pairs.
function headersToNode(h) {
var out = {};
if (!h || typeof h !== 'object') return out;
if (typeof h.forEach !== 'function') return out;
h.forEach(function (v, k) {
if (k != null) out[String(k).toLowerCase()] = String(v);
});
return out;
}
function makeIncoming(resp, onDone) {
var res = Object.create(IncomingMessage.prototype);
attachEE(res);
res.statusCode = resp && typeof resp.status === 'number' ? resp.status : 0;
res.statusMessage = (resp && resp.statusText) || '';
res.headers = headersToNode(resp && resp.headers);
res.httpVersion = '1.1';
res.complete = true;
// The transport buffers the whole body before the response settles, and
// the realm's Response class hands it over via arrayBuffer() — a Promise
// that settles on the following microtask. There is exactly one chunk:
// listeners registered before it settles are queued and fired in
// registration order (data, then end) once it does; listeners registered
// after settle receive data/end on registration.
//
// Node semantics: 'data' chunks are Buffers — byte views of the wire
// body. Consuming via text() folded every invalid-UTF-8 byte into
// U+FFFD (a 256-byte all-values body came back corrupted), so the body
// travels as an ArrayBuffer and is materialised as
// Buffer.from(arrayBuffer) (shares the backing store, no copy).
// setEncoding() switches delivery to a decoded string.
res._bodyAB = null;
res._bodySettled = false;
res._encoding = null;
res._chunk = function () {
if (res._bodyAB === null) return null;
if (res._encoding !== null) {
return Buffer.from(res._bodyAB).toString(res._encoding);
}
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
return Buffer.from(res._bodyAB);
}
return new Uint8Array(res._bodyAB);
};
res._deliverBody = function () {
if (res._bodySettled) return;
res._bodySettled = true;
var chunk = res._chunk();
var ds = res._hh['data'];
if (ds && chunk !== null && chunk.length !== 0) {
for (var i = 0; i < ds.length; i++) ds[i].call(res, chunk);
}
var es = res._hh['end'];
if (es) for (var j = 0; j < es.length; j++) es[j].call(res);
};
res.on = function (ev, fn) {
(res._hh[ev] || (res._hh[ev] = [])).push(fn);
if (res._bodySettled) {
if (ev === 'data') {
var c = res._chunk();
if (c !== null && c.length !== 0) fn.call(res, c);
} else if (ev === 'end') {
fn.call(res);
}
}
return res;
};
res.addListener = res.on;
res.resume = function () { return res; };
res.pause = function () { return res; };
// Node's readable setEncoding: switches 'data' delivery to decoded
// strings (Buffer#toString encodings). Unknown encodings throw at call
// time (Node's ERR_UNKNOWN_ENCODING), never a silent no-op.
res.setEncoding = function (enc) {
var e = (enc === undefined || enc === null) ? 'utf8' : String(enc);
var n = e.toLowerCase();
var ok = n === '' || n === 'utf8' || n === 'utf-8' || n === 'ascii' ||
n === 'latin1' || n === 'binary' || n === 'base64' ||
n === 'base64url' || n === 'hex' ||
n === 'ucs2' || n === 'ucs-2' || n === 'utf16le' || n === 'utf-16le';
if (!ok) throw new TypeError('Unknown encoding: ' + e);
res._encoding = (n === '' || n === 'utf-8') ? 'utf8' : n;
return res;
};
res.destroy = function () { res.complete = true; return res; };
var done = onDone;
var settleBody = function (ab) {
res._bodyAB = ab;
res._deliverBody();
if (done) { var f = done; done = null; f(); }
};
var failBody = function (err) {
// Loud failure — a body read error must never surface as a silent
// empty body (fake-green class).
var e = err instanceof Error ? err : new Error(String(err && err.message ? err.message : err));
var had = res.emit('error', e);
if (!had && typeof console !== 'undefined' && console.error) {
console.error('http: response body read failed:', e.message);
}
settleBody(new ArrayBuffer(0));
};
try {
if (!resp || typeof resp.arrayBuffer !== 'function') {
throw new Error('http: transport resolved without a Response body (arrayBuffer() missing)');
}
resp.arrayBuffer().then(function (ab) {
settleBody(ab instanceof ArrayBuffer ? ab : new ArrayBuffer(0));
}, failBody);
} catch (e) {
failBody(e);
}
return res;
}
// Request body parts: strings are stored as-is; Buffer/TypedArray/DataView/
// ArrayBuffer parts are stored verbatim (byte-exact) and assembled into one
// Uint8Array at fire time. Anything else throws at the write/end call —
// the previous `String(data)` coercion turned a Buffer into "72,101,108"
// (comma-joined bytes) and silently corrupted every binary request body.
function isBytePart(v) {
return !!v && typeof v === 'object' &&
(v instanceof ArrayBuffer || (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(v)));
}
function pushBodyPart(req, data) {
if (data === undefined || data === null) return;
if (typeof data === 'string' || isBytePart(data)) {
req._bodyParts.push(data);
return;
}
throw new TypeError('http: request body chunk must be a string, Buffer, TypedArray or ArrayBuffer');
}
// Transport body argument: all-string parts keep the string fast path
// (identical wire bytes to the historical join); any binary part switches
// to byte-exact assembly — strings encode UTF-8 (TextEncoder), byte parts
// copy their view verbatim.
function buildBodyArg(parts) {
var hasBinary = false;
for (var i = 0; i < parts.length; i++) {
if (typeof parts[i] !== 'string') { hasBinary = true; break; }
}
if (!hasBinary) return parts.join('');
var enc = new TextEncoder();
var chunks = [];
var total = 0;
for (var j = 0; j < parts.length; j++) {
var p = parts[j];
var u;
if (typeof p === 'string') u = enc.encode(p);
else if (p instanceof ArrayBuffer) u = new Uint8Array(p);
else u = new Uint8Array(p.buffer, p.byteOffset, p.byteLength);
chunks.push(u);
total += u.length;
}
var out = new Uint8Array(total);
var off = 0;
for (var k = 0; k < chunks.length; k++) {
out.set(chunks[k], off);
off += chunks[k].length;
}
return out;
}
function fireRequest(req) {
if (req._fired || req.destroyed) return req;
req._fired = true;
var headersJSON = '{}';
try { headersJSON = JSON.stringify(req._headers); } catch (e) {}
var tlsOptsJSON = '{}';
try { tlsOptsJSON = JSON.stringify(req._tlsOpts || {}); } catch (e) {}
var p;
try {
p = req._transport(req._url, req.method, headersJSON, buildBodyArg(req._bodyParts), tlsOptsJSON);
} catch (e) {
settleError(req, e);
return req;
}
p.then(function (resp) {
if (req.destroyed) { req.emit('close'); return; }
// 'close' follows response-body delivery (the request cycle ends when
// the response is fully consumed) — makeIncoming fires onDone once
// Response#text() has settled and data/end were delivered.
var res = makeIncoming(resp, function () { req.emit('close'); });
req.res = res;
try { if (req._cb) req._cb(res); } catch (e) { lateError(req, e); }
req.emit('response', res);
}, function (err) {
if (req.destroyed) { req.emit('close'); return; }
settleError(req, err);
});
return req;
}
function settleError(req, err) {
var e = err instanceof Error ? err : new Error(String(err && err.message ? err.message : err));
var had = req.emit('error', e);
if (!had && typeof console !== 'undefined' && console.error) {
console.error('http: unhandled request error:', e && e.message);
}
req.emit('close');
}
function lateError(req, e) {
if (typeof console !== 'undefined' && console.error) {
console.error('http: response callback threw:', e && e.message);
}
}
function makeRequest(scheme, url, opts, cb, transport) {
var req = Object.create(ClientRequest.prototype);
attachEE(req);
req.method = (opts.method || 'GET').toUpperCase();
req.path = opts.path || '/';
req.host = opts.hostname || opts.host || 'localhost';
req.port = opts.port != null ? Number(opts.port) : (scheme === 'https:' ? 443 : 80);
req._transport = transport;
req.headers = {};
var src = opts.headers || {};
for (var k in src) { if (Object.prototype.hasOwnProperty.call(src, k)) req.headers[k] = src[k]; }
req._headers = req.headers;
req._bodyParts = [];
// Validated eagerly so an invalid opts.body surfaces at request()
// construction, not on a later .end().
pushBodyPart(req, opts.body);
req._url = url;
req._cb = cb || null;
// Node TLS options (https): rejectUnauthorized / ca / servername —
// forwarded to the transport (ignored by plain-http transports).
req._tlsOpts = {};
if (opts.rejectUnauthorized !== undefined) req._tlsOpts.rejectUnauthorized = !!opts.rejectUnauthorized;
if (opts.ca !== undefined && opts.ca !== null) req._tlsOpts.ca = opts.ca;
if (opts.servername) req._tlsOpts.servername = String(opts.servername);
req.aborted = false;
req.destroyed = false;
req._fired = false;
req.res = null;
req.write = function (data) {
if (req._fired) { throw new Error('http: write() after end() — the request is already sent'); }
pushBodyPart(req, data);
return req;
};
req.end = function (data) {
if (!req._fired) pushBodyPart(req, data);
fireRequest(req);
return req;
};
req.setHeader = function (k, v) { req._headers[k] = v; return req; };
req.getHeader = function (k) { return req._headers[k]; };
req.removeHeader = function (k) { delete req._headers[k]; return req; };
req.flushHeaders = function () { return req; };
req.abort = function () { req.aborted = true; req.destroyed = true; return req; };
req.destroy = function () { req.destroyed = true; return req; };
req.setNoDelay = function () { return req; };
req.setSocketKeepAlive = function () { return req; };
req.setTimeout = function (ms, cb2) {
if (cb2) req.on('timeout', cb2);
setTimeout(function () { if (!req.res && !req.destroyed) req.emit('timeout'); }, ms);
return req;
};
return req;
}
function normalizeArgs(a, b, c) {
var url = null, opts = {}, cb = null;
if (typeof a === 'string') {
url = a;
if (typeof b === 'function') cb = b;
else if (b && typeof b === 'object') opts = b;
if (!cb && typeof c === 'function') cb = c;
} else if (a && typeof a === 'object') {
opts = a;
if (typeof b === 'function') cb = b;
} else {
throw new Error('http: request() expects a URL string or an options object');
}
return { url: url, opts: opts, cb: cb };
}
function buildURL(scheme, url, opts) {
if (url) {
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) url = scheme + '//' + url;
return url;
}
var host = opts.hostname || opts.host || 'localhost';
var port = '';
if (opts.port != null && String(host).indexOf(':') < 0) port = ':' + opts.port;
return scheme + '//' + host + port + (opts.path || '/');
}
h.request = function (a, b, c) {
var n = normalizeArgs(a, b, c);
var url = buildURL('http:', n.url, n.opts);
return makeRequest('http:', url, n.opts, n.cb, h.__http_request_async);
};
h.get = function (a, b, c) {
var n = normalizeArgs(a, b, c);
if (!n.opts.method) n.opts.method = 'GET';
var url = buildURL('http:', n.url, n.opts);
var req = makeRequest('http:', url, n.opts, n.cb, h.__http_request_async);
req.end();
return req;
};
// Client-factory for sibling schemes (node:https): same Node contract,
// different transport + URL scheme. Returns {request, get}.
h.__makeClient = function (transport, scheme) {
return {
request: function (a, b, c) {
var n = normalizeArgs(a, b, c);
var url = buildURL(scheme, n.url, n.opts);
return makeRequest(scheme, url, n.opts, n.cb, transport);
},
get: function (a, b, c) {
var n = normalizeArgs(a, b, c);
if (!n.opts.method) n.opts.method = 'GET';
var url = buildURL(scheme, n.url, n.opts);
var req = makeRequest(scheme, url, n.opts, n.cb, transport);
req.end();
return req;
},
};
};
})"#;
thread_local! {
static ACTIVE_APPS: RefCell<Vec<*mut App<false>>> = const { RefCell::new(Vec::new()) };
static LIVENESS_PROBES: RefCell<Vec<fn() -> bool>> = const { RefCell::new(Vec::new()) };
}
pub fn register_liveness_probe(probe: fn() -> bool) {
LIVENESS_PROBES.with(|p| {
let mut p = p.borrow_mut();
if !p.contains(&probe) {
p.push(probe);
}
});
}
pub fn has_active_servers() -> bool {
if ACTIVE_APPS.with(|s| !s.borrow().is_empty()) {
return true;
}
LIVENESS_PROBES.with(|p| p.borrow().iter().any(|probe| probe()))
}
pub unsafe fn register_active_app(app: *mut App<false>) {
if app.is_null() {
return;
}
ACTIVE_APPS.with(|s| {
let mut apps = s.borrow_mut();
if !apps.iter().any(|&p| ::core::ptr::eq(p, app)) {
apps.push(app);
}
});
}
pub unsafe fn unregister_active_app(app: *mut App<false>) {
if app.is_null() {
return;
}
ACTIVE_APPS.with(|s| {
let mut apps = s.borrow_mut();
apps.retain(|&p| !::core::ptr::eq(p, app));
});
}
pub fn listener_fds() -> Vec<i32> {
Vec::new()
}
pub fn install(cx: &mut mozjs::context::JSContext) {
rooted!(&in(cx) let http_obj = unsafe { w2::JS_NewPlainObject(cx) });
if http_obj.get().is_null() {
return;
}
unsafe {
w2::JS_DefineFunction(
cx,
http_obj.handle(),
c"createServer".as_ptr(),
Some(http_create_server),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
http_obj.handle(),
c"__http_request_async".as_ptr(),
Some(http_request),
4,
0,
);
{
let opts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"node:http".as_ptr(), 1);
if !opts.is_null() {
let mut src_text = mozjs::rust::transform_str_to_source_text(
"function Server(opts, cb) { if (typeof opts === 'function') { cb = opts; } if (cb) this.on('request', cb); }\
Server.prototype.listen = function() { return this; };\
Server.prototype.close = function() { return this; };\
Server.prototype.on = function(e, fn) { if (!this._events) this._events = {}; (this._events[e] || (this._events[e] = [])).push(fn); return this; };\
Server.prototype.emit = function(e) { var a = Array.prototype.slice.call(arguments, 1); var ls = this._events && this._events[e]; if (ls) for (var i = 0; i < ls.length; i++) ls[i].apply(this, a); return this; };\
Server",
);
let mut rval = UndefinedValue();
JS::Evaluate2(
cx.raw_cx(),
opts,
&mut src_text,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
libc::free(opts as *mut _);
if rval.is_object() {
rooted!(&in(cx) let ctor_root = rval.to_object());
let server_ctor = ObjectValue(ctor_root.get());
rooted!(&in(cx) let sv = server_ctor);
JS_DefineProperty(
cx.raw_cx(),
http_obj.handle().into(),
c"Server".as_ptr(),
sv.handle().into(),
(JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
);
}
}
}
rooted!(&in(cx) let status_obj = w2::JS_NewPlainObject(cx));
if !status_obj.get().is_null() {
let codes: &[(&str, &str)] = &[
("100", "Continue"),
("101", "Switching Protocols"),
("102", "Processing"),
("200", "OK"),
("201", "Created"),
("202", "Accepted"),
("203", "Non-Authoritative Information"),
("204", "No Content"),
("205", "Reset Content"),
("206", "Partial Content"),
("207", "Multi-Status"),
("208", "Already Reported"),
("226", "IM Used"),
("300", "Multiple Choices"),
("301", "Moved Permanently"),
("302", "Found"),
("303", "See Other"),
("304", "Not Modified"),
("305", "Use Proxy"),
("306", "(Unused)"),
("307", "Temporary Redirect"),
("308", "Permanent Redirect"),
("400", "Bad Request"),
("401", "Unauthorized"),
("402", "Payment Required"),
("403", "Forbidden"),
("404", "Not Found"),
("405", "Method Not Allowed"),
("406", "Not Acceptable"),
("407", "Proxy Authentication Required"),
("408", "Request Timeout"),
("409", "Conflict"),
("410", "Gone"),
("411", "Length Required"),
("412", "Precondition Failed"),
("413", "Payload Too Large"),
("414", "URI Too Long"),
("415", "Unsupported Media Type"),
("416", "Range Not Satisfiable"),
("417", "Expectation Failed"),
("418", "I'm a Teapot"),
("421", "Misdirected Request"),
("422", "Unprocessable Entity"),
("423", "Locked"),
("424", "Failed Dependency"),
("425", "Too Early"),
("426", "Upgrade Required"),
("428", "Precondition Required"),
("429", "Too Many Requests"),
("431", "Request Header Fields Too Large"),
("451", "Unavailable For Legal Reasons"),
("500", "Internal Server Error"),
("501", "Not Implemented"),
("502", "Bad Gateway"),
("503", "Service Unavailable"),
("504", "Gateway Timeout"),
("505", "HTTP Version Not Supported"),
("506", "Variant Also Negotiates"),
("507", "Insufficient Storage"),
("508", "Loop Detected"),
("509", "Bandwidth Limit Exceeded"),
("510", "Not Extended"),
("511", "Network Authentication Required"),
];
for (code, msg) in codes {
let c_code = ZBox::from_bytes(code.as_bytes());
let c_msg = ZBox::from_bytes(msg.as_bytes());
let js_msg = JS_NewStringCopyZ(cx.raw_cx(), c_msg.as_ptr());
if !js_msg.is_null() {
let mv = StringValue(&*js_msg);
rooted!(&in(cx) let mvr = mv);
JS_DefineProperty(
cx.raw_cx(),
status_obj.handle().into(),
c_code.as_ptr(),
mvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let status_val = ObjectValue(status_obj.get());
rooted!(&in(cx) let status_r = status_val);
JS_DefineProperty(
cx.raw_cx(),
http_obj.handle().into(),
c"STATUS_CODES".as_ptr(),
status_r.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
{
let methods = [
"ACL",
"BIND",
"CHECKOUT",
"CONNECT",
"COPY",
"DELETE",
"GET",
"HEAD",
"LINK",
"LOCK",
"M-SEARCH",
"MERGE",
"MKACTIVITY",
"MKCALENDAR",
"MKCOL",
"MOVE",
"NOTIFY",
"OPTIONS",
"PATCH",
"POST",
"PROPFIND",
"PROPPATCH",
"PURGE",
"PUT",
"REBIND",
"REPORT",
"SEARCH",
"SOURCE",
"SUBSCRIBE",
"TRACE",
"UNBIND",
"UNLINK",
"UNLOCK",
"UNSUBSCRIBE",
];
rooted!(&in(cx) let arr = w2::NewArrayObject1(cx, methods.len()));
if !arr.get().is_null() {
for (i, m) in methods.iter().enumerate() {
let c_m = ZBox::from_bytes(m.as_bytes());
let js_m = JS_NewStringCopyZ(cx.raw_cx(), c_m.as_ptr());
if !js_m.is_null() {
rooted!(&in(cx) let mv = StringValue(&*js_m));
JS_DefineElement(
cx.raw_cx(),
arr.handle().into(),
i as u32,
mv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let av = ObjectValue(arr.get());
rooted!(&in(cx) let avr = av);
JS_DefineProperty(
cx.raw_cx(),
http_obj.handle().into(),
c"METHODS".as_ptr(),
avr.handle().into(),
(JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
);
}
}
rooted!(&in(cx) let mr_val = mozjs::jsval::Int32Value(21));
JS_DefineProperty(
cx.raw_cx(),
http_obj.handle().into(),
c"maxRedirects".as_ptr(),
mr_val.handle().into(),
(JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
);
{
let validate_src = r#"(function(h){
var validHeaderNameRegex = /^[!#$%&'*+.^_`|0-9A-Za-z-]+$/;
var validHeaderValueRegex = /^[^\t\n\r\x00]*$/;
function validateHeaderName(name) {
if (typeof name !== 'string' || !validHeaderNameRegex.test(name)) {
throw new TypeError('Header name must be a valid HTTP token: ' + String(name));
}
}
function validateHeaderValue(name, value) {
if (value === undefined) {
throw new TypeError('Invalid header value for ' + name + ': undefined');
}
if (typeof value !== 'string' || !validHeaderValueRegex.test(value)) {
throw new TypeError('Invalid header value for ' + name + ': ' + String(value));
}
}
h.validateHeaderName = validateHeaderName;
h.validateHeaderValue = validateHeaderValue;
})"#;
let mut vsrc = mozjs::rust::transform_str_to_source_text(validate_src);
let mut vval = UndefinedValue();
let vh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut vval,
};
let vopts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<http-validate>".as_ptr(), 1);
if !vopts.is_null() {
if JS::Evaluate2(cx.raw_cx(), vopts, &mut vsrc, vh) && vval.is_object() {
let wrapped_cx =
mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx.raw_cx()));
rooted!(&in(wrapped_cx) let global_root = CurrentGlobalOrNull(cx.raw_cx()));
rooted!(&in(wrapped_cx) let http_val_root = ObjectValue(http_obj.get()));
let args_arr = HandleValueArray {
length_: 1,
elements_: &http_val_root.get() as *const Value,
};
let mut call_rval = UndefinedValue();
let call_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut call_rval,
};
rooted!(&in(wrapped_cx) let factory_obj = vval.to_object());
rooted!(&in(wrapped_cx) let factory_obj_h = ObjectValue(factory_obj.get()));
JS_CallFunctionValue(
cx.raw_cx(),
global_root.handle().into(),
factory_obj_h.handle().into(),
&args_arr,
call_rval_h,
);
}
libc::free(vopts as *mut _);
}
}
{
let classes_src = HTTP_CLIENT_JS;
let mut csrc = mozjs::rust::transform_str_to_source_text(classes_src);
let mut cval = UndefinedValue();
let ch = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cval,
};
let copts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<http-classes>".as_ptr(), 1);
if !copts.is_null() {
if JS::Evaluate2(cx.raw_cx(), copts, &mut csrc, ch) && cval.is_object() {
let wrapped_cx =
mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx.raw_cx()));
rooted!(&in(wrapped_cx) let global_root = CurrentGlobalOrNull(cx.raw_cx()));
rooted!(&in(wrapped_cx) let http_val_root = ObjectValue(http_obj.get()));
let args_arr = HandleValueArray {
length_: 1,
elements_: &http_val_root.get() as *const Value,
};
let mut call_rval = UndefinedValue();
let call_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut call_rval,
};
rooted!(&in(wrapped_cx) let factory_obj = cval.to_object());
rooted!(&in(wrapped_cx) let factory_obj_h = ObjectValue(factory_obj.get()));
JS_CallFunctionValue(
cx.raw_cx(),
global_root.handle().into(),
factory_obj_h.handle().into(),
&args_arr,
call_rval_h,
);
}
libc::free(copts as *mut _);
}
}
rooted!(&in(cx) let agent_obj = w2::JS_NewPlainObject(cx));
if !agent_obj.get().is_null() {
let av = ObjectValue(agent_obj.get());
rooted!(&in(cx) let avr = av);
JS_DefineProperty(
cx.raw_cx(),
http_obj.handle().into(),
c"globalAgent".as_ptr(),
avr.handle().into(),
(JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
);
let agent_ctor_src = "function Agent(opts) { for (var k in opts) this[k] = opts[k]; }";
let mut asrc = mozjs::rust::transform_str_to_source_text(agent_ctor_src);
let mut aval = UndefinedValue();
let ah = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut aval,
};
let aopts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<http-agent>".as_ptr(), 1);
if !aopts.is_null() {
if JS::Evaluate2(cx.raw_cx(), aopts, &mut asrc, ah) && aval.is_object() {
let av2 = ObjectValue(aval.to_object());
rooted!(&in(cx) let av2r = av2);
JS_DefineProperty(
cx.raw_cx(),
http_obj.handle().into(),
c"Agent".as_ptr(),
av2r.handle().into(),
(JSPROP_ENUMERATE | JSPROP_PERMANENT) as u32,
);
}
libc::free(aopts as *mut _);
}
}
}
cache_builtin(cx, "http", http_obj.get());
}
struct ServerUserData {
cx: *mut JSContext,
global_key: String,
handler_key: String,
server_obj_key: String,
}
impl ServerUserData {
fn new(
cx: *mut JSContext,
global: *mut JSObject,
handler: *mut JSObject,
server_obj: *mut JSObject,
) -> Self {
let server_id = NEXT_SERVER_ID.fetch_add(1, Ordering::Relaxed);
let global_key = format!("http_server_{}_global", server_id);
let handler_key = format!("http_server_{}_handler", server_id);
let server_obj_key = format!("http_server_{}_server_obj", server_id);
gc_store_insert_ns(cx, "http", &global_key, global);
gc_store_insert_ns(cx, "http", &handler_key, handler);
gc_store_insert_ns(cx, "http", &server_obj_key, server_obj);
Self {
cx,
global_key,
handler_key,
server_obj_key,
}
}
fn handler(&self) -> Option<*mut JSObject> {
gc_store_get_ns(self.cx, "http", &self.handler_key)
}
fn server_obj(&self) -> Option<*mut JSObject> {
gc_store_get_ns(self.cx, "http", &self.server_obj_key)
}
fn cleanup(&self) {
gc_store_remove_ns(self.cx, "http", &self.global_key);
gc_store_remove_ns(self.cx, "http", &self.handler_key);
gc_store_remove_ns(self.cx, "http", &self.server_obj_key);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn uws_route_handler(
res: *mut bun_uws_sys::response::c::uws_res,
req: *mut bun_uws_sys::Request,
user_data: *mut ::std::ffi::c_void,
) {
if res.is_null() || req.is_null() || user_data.is_null() {
return;
}
let ud = &*(user_data as *const ServerUserData);
let cx = ud.cx;
if cx.is_null() {
return;
}
let raw_cx = cx;
let res_mut = Response::<false>::cast_res(res);
let global = match bao_engine::context::thread_realm_global() {
Some(g) if !g.is_null() => g,
_ => {
eprintln!("[node:http] no JS realm on this thread — responding 500");
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"no JS realm", true);
return;
}
};
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global_root = global);
let mut realm = AutoRealm::new_from_handle(cx_ref, global_root.handle());
let cx_ref: &mut mozjs::context::JSContext = &mut realm;
let handler = match ud.handler() {
Some(h) if !h.is_null() => h,
_ => {
eprintln!(
"[node:http] request handler unavailable (key {}) — responding 500",
ud.handler_key
);
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"no request handler", true);
return;
}
};
rooted!(&in(cx_ref) let handler_val_root = ObjectValue(handler));
let req_ref = bun_opaque::opaque_deref_mut(req);
let method_bytes = req_ref.method();
let url_bytes = req_ref.url();
let method_upper = method_bytes.to_ascii_uppercase();
let method_str = ::std::str::from_utf8_unchecked(&method_upper);
let url_str = ::std::str::from_utf8_unchecked(url_bytes);
rooted!(&in(cx_ref) let req_obj = unsafe { w2::JS_NewPlainObject(cx_ref) });
if req_obj.get().is_null() {
return;
}
let c_method = ZBox::from_bytes(method_str.as_bytes());
let js_method = JS_NewStringCopyZ(raw_cx, c_method.as_ptr());
if !js_method.is_null() {
let mv = StringValue(&*js_method);
rooted!(&in(cx_ref) let mvr = mv);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"method".as_ptr(),
mvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let c_url = ZBox::from_bytes(url_str.as_bytes());
let js_url = JS_NewStringCopyZ(raw_cx, c_url.as_ptr());
if !js_url.is_null() {
let uv = StringValue(&*js_url);
rooted!(&in(cx_ref) let uvr = uv);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"url".as_ptr(),
uvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let headers_obj = w2::JS_NewPlainObject(cx_ref));
if !headers_obj.get().is_null() {
let mut header_pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
req_ref.for_each_header(
|pairs: &mut Vec<(Vec<u8>, Vec<u8>)>, name: &[u8], value: &[u8]| {
pairs.push((name.to_vec(), value.to_vec()));
},
&mut header_pairs as *mut Vec<(Vec<u8>, Vec<u8>)>,
);
for (name, value) in &header_pairs {
let c_k = ZBox::from_bytes(name);
let c_v = ZBox::from_bytes(value);
let js_v = JS_NewStringCopyZ(raw_cx, c_v.as_ptr());
if !js_v.is_null() {
let hv = StringValue(&*js_v);
rooted!(&in(cx_ref) let hvr = hv);
JS_DefineProperty(
raw_cx,
headers_obj.handle().into(),
c_k.as_ptr(),
hvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let hdrs_val = ObjectValue(headers_obj.get());
rooted!(&in(cx_ref) let hdrs_r = hdrs_val);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"headers".as_ptr(),
hdrs_r.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let upgrade_header = req_ref
.header(b"upgrade")
.map(|h| h.to_vec())
.unwrap_or_default();
let is_ws_upgrade = upgrade_header.eq_ignore_ascii_case(b"websocket");
if is_ws_upgrade {
let mut had_upgrade_listener = false;
rooted!(&in(cx_ref) let socket_obj = w2::JS_NewPlainObject(cx_ref));
if !socket_obj.get().is_null() {
rooted!(&in(cx_ref) let upgrade_res_obj = w2::JS_NewPlainObject(cx_ref));
if !upgrade_res_obj.get().is_null() {
w2::JS_DefineFunction(
cx_ref,
upgrade_res_obj.handle(),
c"writeHead".as_ptr(),
Some(res_write_head),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
upgrade_res_obj.handle(),
c"end".as_ptr(),
Some(res_end),
1,
JSPROP_ENUMERATE as u32,
);
let status_val = Int32Value(101);
rooted!(&in(cx_ref) let sv = status_val);
JS_DefineProperty(
raw_cx,
upgrade_res_obj.handle().into(),
c"statusCode".as_ptr(),
sv.handle().into(),
JSPROP_ENUMERATE as u32,
);
let res_ptr_val = mozjs::jsval::PrivateValue(res as *const core::ffi::c_void);
rooted!(&in(cx_ref) let rv = res_ptr_val);
JS_DefineProperty(
raw_cx,
upgrade_res_obj.handle().into(),
c"_uwsRes".as_ptr(),
rv.handle().into(),
0,
);
}
if let Some(server_obj) = ud.server_obj() {
if !server_obj.is_null() {
rooted!(&in(cx_ref) let server_root = server_obj);
let mut emit_val = UndefinedValue();
JS_GetProperty(
raw_cx,
server_root.handle().into(),
c"emit".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut emit_val,
},
);
if emit_val.is_object() {
rooted!(&in(cx_ref) let emit_fn = emit_val.to_object());
let event_name_str = JS_NewStringCopyZ(raw_cx, c"upgrade".as_ptr());
if !event_name_str.is_null() {
let ev_val = StringValue(&*event_name_str);
let args_vals = [
ev_val,
ObjectValue(req_obj.get()),
ObjectValue(socket_obj.get()),
ObjectValue(if !upgrade_res_obj.get().is_null() {
upgrade_res_obj.get()
} else {
socket_obj.get()
}),
];
let call_args = HandleValueArray {
length_: 4,
elements_: args_vals.as_ptr(),
};
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let emit_fn_val = ObjectValue(emit_fn.get());
rooted!(&in(cx_ref) let emit_fn_root = emit_fn_val);
JS_CallFunctionValue(
raw_cx,
server_root.handle().into(),
emit_fn_root.handle().into(),
&call_args,
rval_h,
);
JS_ClearPendingException(raw_cx);
had_upgrade_listener = rval.is_boolean() && rval.to_boolean();
}
}
}
}
}
{
let res_mut = Response::<false>::cast_res(res);
let responded = (*res_mut).state().is_http_status_called();
if !had_upgrade_listener || !responded {
(*res_mut).write_status(b"426 Upgrade Required");
(*res_mut).end(b"Upgrade Required", true);
}
}
return;
}
rooted!(&in(cx_ref) let res_obj = w2::JS_NewPlainObject(cx_ref));
if res_obj.get().is_null() {
return;
}
w2::JS_DefineFunction(
cx_ref,
res_obj.handle(),
c"writeHead".as_ptr(),
Some(res_write_head),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
res_obj.handle(),
c"write".as_ptr(),
Some(res_write),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
res_obj.handle(),
c"end".as_ptr(),
Some(res_end),
1,
JSPROP_ENUMERATE as u32,
);
let status_val = Int32Value(200);
rooted!(&in(cx_ref) let sv = status_val);
JS_DefineProperty(
raw_cx,
res_obj.handle().into(),
c"statusCode".as_ptr(),
sv.handle().into(),
JSPROP_ENUMERATE as u32,
);
let res_ptr_val = mozjs::jsval::PrivateValue(res as *const core::ffi::c_void);
rooted!(&in(cx_ref) let rv = res_ptr_val);
JS_DefineProperty(
raw_cx,
res_obj.handle().into(),
c"_uwsRes".as_ptr(),
rv.handle().into(),
0,
);
let args_vals = [ObjectValue(req_obj.get()), ObjectValue(res_obj.get())];
let call_args = HandleValueArray {
length_: 2,
elements_: args_vals.as_ptr(),
};
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = JS_CallFunctionValue(
raw_cx,
global_root.handle().into(),
handler_val_root.handle().into(),
&call_args,
rval_h,
);
if !ok {
JS_ClearPendingException(raw_cx);
eprintln!("[node:http] request handler threw — responding 500");
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"request handler threw", true);
return;
}
if !(*res_mut).state().is_http_end_called() {
eprintln!("[node:http] request handler returned without responding — responding 500");
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"handler did not respond", true);
}
}
#[inline]
fn val_is_private(v: &JSVal) -> bool {
v.is_double() && (v.asBits_ & 0xFFFF000000000000) == 0
}
#[inline]
unsafe fn get_uws_res(
cx: *mut JSContext,
obj: *mut JSObject,
) -> *mut bun_uws_sys::response::c::uws_res {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let mut ptr_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"_uwsRes".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ptr_val,
},
);
if !val_is_private(&ptr_val) {
return core::ptr::null_mut();
}
ptr_val.to_private() as *mut bun_uws_sys::response::c::uws_res
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn res_write_head(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
if argc > 0 {
let v = *args.get(0).ptr;
if v.is_int32() {
let status = v.to_int32();
let this = args.thisv();
rooted!(&in(cx_ref) let obj = this.to_object());
rooted!(&in(cx_ref) let v_root = v);
JS_SetProperty(
cx,
obj.handle().into(),
c"statusCode".as_ptr(),
v_root.handle().into(),
);
let uws_res = get_uws_res(cx, obj.get());
if !uws_res.is_null() {
let status_str = format!("{} ", status);
let res_mut = Response::<false>::cast_res(uws_res);
(*res_mut).write_status(status_str.as_bytes());
}
if argc > 1 {
let hdrs_val = *args.get(1).ptr;
if hdrs_val.is_object() {
rooted!(&in(cx_ref) let hdrs_obj = hdrs_val.to_object());
let uws_res = get_uws_res(cx, obj.get());
if !uws_res.is_null() {
let res_mut = Response::<false>::cast_res(uws_res);
let mut ids = mozjs::rust::IdVector::new(cx);
if w2::GetPropertyKeys(
cx_ref,
hdrs_obj.handle().into(),
JSITER_OWNONLY as u32,
ids.handle_mut(),
) {
for jsid in &*ids {
if !jsid.is_string() {
continue;
}
let key_str = jsid.to_string();
let key = mozjs::conversions::unsafe_jsstr_to_string(
cx,
NonNull::new_unchecked(key_str),
);
let mut hv = UndefinedValue();
let c_key = ZBox::from_bytes(key.as_bytes());
JS_GetProperty(
cx,
hdrs_obj.handle().into(),
c_key.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut hv,
},
);
if hv.is_string() {
let val = crate::js_to_rust_string(cx, hv);
let key_lower = key.to_ascii_lowercase();
let c_val = ZBox::from_bytes(val.as_bytes());
(*res_mut).write_header(key_lower.as_bytes(), c_val.as_bytes());
}
}
}
}
}
}
}
}
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let this_root = args.thisv().to_object());
args.rval().set(ObjectValue(this_root.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn res_append_chunk(cx: *mut JSContext, obj: *mut JSObject, v: JSVal) -> bool {
if v.is_undefined() || v.is_null() {
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let part_val: Value = if v.is_string() {
v
} else if let Some(bytes) = crate::node_buffer::collect_byte_view(cx, v) {
let ta = crate::globals::create_buffer_object(cx, &bytes);
if ta.is_null() {
JS_ReportErrorUTF8(
cx,
c"%s".as_ptr(),
c"[node:http] failed to allocate response body chunk".as_ptr(),
);
return false;
}
ObjectValue(ta)
} else {
let msg = ZBox::from_bytes(
"[node:http] res.write/end chunk must be a string, Buffer, TypedArray or ArrayBuffer"
.as_bytes(),
);
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
};
rooted!(&in(cx_ref) let part_root = part_val);
let mut chunks_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"_bodyChunks".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut chunks_val,
},
);
if !chunks_val.is_object() {
rooted!(&in(cx_ref) let arr = w2::NewArrayObject1(cx_ref, 0));
if arr.get().is_null() {
JS_ReportErrorUTF8(
cx,
c"%s".as_ptr(),
c"[node:http] failed to allocate response body chunk list".as_ptr(),
);
return false;
}
let av = ObjectValue(arr.get());
rooted!(&in(cx_ref) let av_root = av);
JS_SetProperty(
cx,
obj_root.handle().into(),
c"_bodyChunks".as_ptr(),
av_root.handle().into(),
);
rooted!(&in(cx_ref) let arr2 = arr.get());
return JS_DefineElement(
cx,
arr2.handle().into(),
0,
part_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let chunks_obj = chunks_val.to_object());
let mut len_val = UndefinedValue();
JS_GetProperty(
cx,
chunks_obj.handle().into(),
c"length".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
},
);
let next_index: u32 = if len_val.is_int32() {
len_val.to_int32().max(0) as u32
} else {
0
};
JS_DefineElement(
cx,
chunks_obj.handle().into(),
next_index,
part_root.handle().into(),
JSPROP_ENUMERATE as u32,
)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn res_collect_body(cx: *mut JSContext, obj: *mut JSObject) -> Vec<u8> {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj_root = obj);
let mut chunks_val = UndefinedValue();
JS_GetProperty(
cx,
obj_root.handle().into(),
c"_bodyChunks".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut chunks_val,
},
);
if !chunks_val.is_object() {
return Vec::new();
}
rooted!(&in(cx_ref) let chunks_obj = chunks_val.to_object());
let mut len_val = UndefinedValue();
JS_GetProperty(
cx,
chunks_obj.handle().into(),
c"length".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
},
);
let count: u32 = if len_val.is_int32() {
len_val.to_int32().max(0) as u32
} else {
0
};
let mut out: Vec<u8> = Vec::new();
for i in 0..count {
let mut elem = UndefinedValue();
if !JS_GetElement(
cx,
chunks_obj.handle().into(),
i,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut elem,
},
) {
break;
}
if elem.is_string() {
out.extend_from_slice(crate::js_to_rust_string(cx, elem).as_bytes());
} else if elem.is_object() {
match crate::node_buffer::collect_byte_view(cx, elem) {
Some(bytes) => out.extend_from_slice(&bytes),
None => eprintln!("[node:http] response body chunk {} was not extractable", i),
}
}
}
out
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn res_write(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = args.thisv().to_object());
let mut ended_val = UndefinedValue();
JS_GetProperty(
cx,
obj.handle().into(),
c"_ended".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ended_val,
},
);
if ended_val.is_boolean() && ended_val.to_boolean() {
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c"write after end".as_ptr());
return false;
}
if argc > 0 {
let v = *args.get(0).ptr;
if !res_append_chunk(cx, obj.get(), v) {
return false;
}
}
args.rval().set(ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn res_end(cx: *mut JSContext, argc: u32, vp: *mut mozjs::jsval::JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = args.thisv().to_object());
let mut ended_val = UndefinedValue();
JS_GetProperty(
cx,
obj.handle().into(),
c"_ended".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ended_val,
},
);
if ended_val.is_boolean() && ended_val.to_boolean() {
args.rval().set(ObjectValue(obj.get()));
return true;
}
if argc > 0 {
let v = *args.get(0).ptr;
if !res_append_chunk(cx, obj.get(), v) {
return false;
}
}
let body = res_collect_body(cx, obj.get());
let uws_res = get_uws_res(cx, obj.get());
if !uws_res.is_null() {
let res_mut = Response::<false>::cast_res(uws_res);
let mut status_val = Int32Value(200);
let status_mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut status_val,
};
JS_GetProperty(cx, obj.handle().into(), c"statusCode".as_ptr(), status_mh);
let status = if status_val.is_int32() {
status_val.to_int32()
} else {
200
};
if !(*res_mut).state().is_http_status_called() {
let status_str = format!("{} ", status);
(*res_mut).write_status(status_str.as_bytes());
}
(*res_mut).end(&body, false);
}
rooted!(&in(cx_ref) let ended_flag = mozjs::jsval::BooleanValue(true));
JS_SetProperty(
cx,
obj.handle().into(),
c"_ended".as_ptr(),
ended_flag.handle().into(),
);
args.rval().set(ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn http_create_server(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let server_obj = unsafe { w2::JS_NewPlainObject(cx_ref) });
if server_obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
if argc > 0 {
let v = *args.get(0).ptr;
if v.is_object() {
rooted!(&in(cx_ref) let cb_obj = v.to_object());
let cb_val = ObjectValue(cb_obj.get());
rooted!(&in(cx_ref) let cb_root = cb_val);
JS_DefineProperty(
cx,
server_obj.handle().into(),
c"_onRequest".as_ptr(),
cb_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let global = CurrentGlobalOrNull(cx));
if !global.get().is_null() {
JS_SetProperty(
cx,
global.handle().into(),
c"_httpRequestHandler".as_ptr(),
cb_root.handle().into(),
);
}
}
}
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"listen".as_ptr(),
Some(server_listen),
3,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"close".as_ptr(),
Some(server_close),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"address".as_ptr(),
Some(server_address),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"on".as_ptr(),
Some(crate::node_events::ee_on),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"addListener".as_ptr(),
Some(crate::node_events::ee_on),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"once".as_ptr(),
Some(crate::node_events::ee_once),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"off".as_ptr(),
Some(crate::node_events::ee_off),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"removeListener".as_ptr(),
Some(crate::node_events::ee_off),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"emit".as_ptr(),
Some(crate::node_events::ee_emit),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"prependListener".as_ptr(),
Some(crate::node_events::ee_prepend),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx_ref,
server_obj.handle(),
c"removeAllListeners".as_ptr(),
Some(crate::node_events::ee_remove_all),
1,
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(server_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn server_listen(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let port: u16 = if argc > 0 {
let v = *args.get(0).ptr;
if v.is_int32() {
v.to_int32() as u16
} else if v.is_double() {
v.to_double() as u16
} else {
3000
}
} else {
3000
};
let callback = if argc > 2 {
let v = *args.get(2).ptr;
if v.is_object() {
rooted!(&in(cx_ref) let cb = v.to_object());
Some(cb.get())
} else {
None
}
} else if argc > 1 {
let v = *args.get(1).ptr;
if v.is_object() {
rooted!(&in(cx_ref) let cb = v.to_object());
Some(cb.get())
} else {
None
}
} else {
None
};
let opts = BunSocketContextOptions::default();
let app_ptr = match App::<false>::create(&opts) {
Some(p) => p,
None => {
let msg = format!("Failed to create HTTP server on port {}", port);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
};
unsafe { (*app_ptr).set_is_node_http(true) };
let this = args.thisv();
rooted!(&in(cx_ref) let server_obj = this.to_object());
let mut handler_val = UndefinedValue();
let handler_mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut handler_val,
};
JS_GetProperty(
cx,
server_obj.handle().into(),
c"_onRequest".as_ptr(),
handler_mh,
);
rooted!(&in(cx_ref) let global = CurrentGlobalOrNull(cx));
if global.get().is_null() || !handler_val.is_object() {
App::<false>::destroy(app_ptr);
let msg = ZBox::from_bytes("http.createServer requires a request handler".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
rooted!(&in(cx_ref) let handler_root = handler_val.to_object());
let ud = Box::new(ServerUserData::new(
cx,
global.get(),
handler_root.get(),
server_obj.get(),
));
let ud_ptr = Box::into_raw(ud) as *mut ::std::ffi::c_void;
let safe_handler: Option<
extern "C" fn(
*mut bun_uws_sys::response::c::uws_res,
*mut bun_uws_sys::Request,
*mut ::std::ffi::c_void,
),
> = unsafe {
::std::mem::transmute(Some(
uws_route_handler
as unsafe extern "C" fn(
*mut bun_uws_sys::response::c::uws_res,
*mut bun_uws_sys::Request,
*mut ::std::ffi::c_void,
),
))
};
(*app_ptr).any(b"/*", safe_handler, ud_ptr);
{
let ud_val = mozjs::jsval::PrivateValue(ud_ptr as *const core::ffi::c_void);
rooted!(&in(cx_ref) let udv = ud_val);
JS_DefineProperty(
cx,
server_obj.handle().into(),
c"_udPtr".as_ptr(),
udv.handle().into(),
0,
);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn node_http_listen_cb(
listen_socket: *mut bun_uws_sys::listen_socket::ListenSocket,
user_data: *mut ::std::ffi::c_void,
) {
if !listen_socket.is_null() && !user_data.is_null() {
let ls_ref = bun_opaque::opaque_deref_mut(listen_socket);
let ls_port = ls_ref.get_local_port();
if ls_port > 0 {
*(user_data as *mut u16) = ls_port as u16;
}
}
}
let safe_listen_cb: extern "C" fn(
*mut bun_uws_sys::listen_socket::ListenSocket,
*mut ::std::ffi::c_void,
) = unsafe {
::std::mem::transmute(
node_http_listen_cb
as unsafe extern "C" fn(
*mut bun_uws_sys::listen_socket::ListenSocket,
*mut ::std::ffi::c_void,
),
)
};
let mut actual_port: u16 = 0;
(*app_ptr).listen(
port as i32,
safe_listen_cb,
&mut actual_port as *mut u16 as *mut ::std::ffi::c_void,
);
let effective_port: u16 = if port == 0 { actual_port } else { port };
{
let app_ptr_val = mozjs::jsval::PrivateValue(app_ptr as *const core::ffi::c_void);
rooted!(&in(cx_ref) let apv = app_ptr_val);
JS_DefineProperty(
cx,
server_obj.handle().into(),
c"_appPtr".as_ptr(),
apv.handle().into(),
0,
);
}
rooted!(&in(cx_ref) let port_root = Int32Value(effective_port as i32));
JS_DefineProperty(
cx,
server_obj.handle().into(),
c"_listeningPort".as_ptr(),
port_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let listening_root = mozjs::jsval::BooleanValue(true));
JS_DefineProperty(
cx,
server_obj.handle().into(),
c"listening".as_ptr(),
listening_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
ACTIVE_APPS.with(|s| s.borrow_mut().push(app_ptr));
if let Some(cb) = callback {
rooted!(&in(cx_ref) let fval_root = ObjectValue(cb));
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
JS_CallFunctionValue(
cx,
global.handle().into(),
fval_root.handle().into(),
&HandleValueArray::empty(),
rval_h,
);
JS_ClearPendingException(cx);
}
args.rval().set(ObjectValue(server_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn server_close(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let this = args.thisv();
rooted!(&in(cx_ref) let server_obj = this.to_object());
let close_cb = if argc > 0 && (*args.get(0).ptr).is_object() {
let cb_obj = (*args.get(0).ptr).to_object();
if unsafe { JS_ObjectIsFunction(cb_obj) } {
Some(cb_obj)
} else {
None
}
} else {
None
};
let had_live_app: bool;
let mut app_ptr_val = UndefinedValue();
JS_GetProperty(
cx,
server_obj.handle().into(),
c"_appPtr".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut app_ptr_val,
},
);
let app_ptr = if val_is_private(&app_ptr_val) {
app_ptr_val.to_private() as *mut App<false>
} else {
core::ptr::null_mut()
};
had_live_app = !app_ptr.is_null();
if !app_ptr.is_null() {
(*app_ptr).close();
App::<false>::destroy(app_ptr);
unsafe {
unregister_active_app(app_ptr);
}
rooted!(&in(cx_ref) let undef_root = UndefinedValue());
JS_SetProperty(
cx,
server_obj.handle().into(),
c"_appPtr".as_ptr(),
undef_root.handle().into(),
);
}
let mut ud_ptr_val = UndefinedValue();
JS_GetProperty(
cx,
server_obj.handle().into(),
c"_udPtr".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ud_ptr_val,
},
);
let ud_ptr = if val_is_private(&ud_ptr_val) {
ud_ptr_val.to_private() as *mut ServerUserData
} else {
core::ptr::null_mut()
};
if !ud_ptr.is_null() {
let ud = unsafe { Box::from_raw(ud_ptr) };
ud.cleanup();
rooted!(&in(cx_ref) let undef_root2 = UndefinedValue());
JS_SetProperty(
cx,
server_obj.handle().into(),
c"_udPtr".as_ptr(),
undef_root2.handle().into(),
);
}
if let Some(cb) = close_cb {
rooted!(&in(cx_ref) let cb_root = cb);
rooted!(&in(cx_ref) let cb_fn = ObjectValue(cb_root.get()));
rooted!(&in(cx_ref) let undef_this = ::std::ptr::null_mut::<JSObject>());
if had_live_app {
let empty_args = HandleValueArray {
length_: 0,
elements_: [].as_ptr(),
};
let mut rval = UndefinedValue();
let _ = JS_CallFunctionValue(
cx,
undef_this.handle().into(),
cb_fn.handle().into(),
&empty_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
} else {
let mut err_val = UndefinedValue();
let mut built_err = false;
let global = JS::CurrentGlobalOrNull(cx);
if !global.is_null() {
rooted!(&in(cx_ref) let global_root = global);
let mut ctor_val = UndefinedValue();
JS_GetProperty(
cx,
global_root.handle().into(),
c"Error".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ctor_val,
},
);
if ctor_val.is_object() {
rooted!(&in(cx_ref) let ctor_obj = ctor_val.to_object());
rooted!(&in(cx_ref) let ctor_fn = ObjectValue(ctor_obj.get()));
let c_msg = ZBox::from_bytes(b"Server is not running");
let msg_js = JS_NewStringCopyZ(cx, c_msg.as_ptr());
if !msg_js.is_null() {
rooted!(&in(cx_ref) let msg_root = StringValue(&*msg_js));
let elems = [msg_root.get()];
let call_args = HandleValueArray {
length_: 1,
elements_: elems.as_ptr(),
};
let mut e_val = UndefinedValue();
if JS_CallFunctionValue(
cx,
undef_this.handle().into(),
ctor_fn.handle().into(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut e_val,
},
) && e_val.is_object()
{
err_val = e_val;
built_err = true;
}
}
}
}
rooted!(&in(cx_ref) let arg_val = if built_err {
err_val
} else {
UndefinedValue()
});
let elems = [arg_val.get()];
let cb_args = HandleValueArray {
length_: 1,
elements_: elems.as_ptr(),
};
let mut rval = UndefinedValue();
let _ = JS_CallFunctionValue(
cx,
undef_this.handle().into(),
cb_fn.handle().into(),
&cb_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
}
}
if had_live_app {
let mut emit_val = UndefinedValue();
JS_GetProperty(
cx,
server_obj.handle().into(),
c"emit".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut emit_val,
},
);
if emit_val.is_object() {
rooted!(&in(cx_ref) let emit_fn = emit_val.to_object());
rooted!(&in(cx_ref) let emit_val_fn = ObjectValue(emit_fn.get()));
let ev_str = JS_NewStringCopyZ(cx, c"close".as_ptr());
if !ev_str.is_null() {
rooted!(&in(cx_ref) let ev_root = StringValue(&*ev_str));
let elems = [ev_root.get()];
let call_args = HandleValueArray {
length_: 1,
elements_: elems.as_ptr(),
};
let mut rval = UndefinedValue();
let _ = JS_CallFunctionValue(
cx,
server_obj.handle().into(),
emit_val_fn.handle().into(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
}
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn server_address(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let addr_obj = unsafe { w2::JS_NewPlainObject(cx_ref) });
if addr_obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let this = args.thisv();
rooted!(&in(cx_ref) let server_obj = this.to_object());
let mut port_val = UndefinedValue();
let port_mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut port_val,
};
JS_GetProperty(
cx,
server_obj.handle().into(),
c"_listeningPort".as_ptr(),
port_mh,
);
if port_val.is_int32() {
let p = port_val.to_int32();
rooted!(&in(cx_ref) let pvr = Int32Value(p));
JS_DefineProperty(
cx,
addr_obj.handle().into(),
c"port".as_ptr(),
pvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
let c_family = ZBox::from_bytes("IPv4".as_bytes());
let js_family = JS_NewStringCopyZ(cx, c_family.as_ptr());
if !js_family.is_null() {
let fv = StringValue(&*js_family);
rooted!(&in(cx_ref) let fvr = fv);
JS_DefineProperty(
cx,
addr_obj.handle().into(),
c"family".as_ptr(),
fvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let c_addr = ZBox::from_bytes("0.0.0.0".as_bytes());
let js_addr = JS_NewStringCopyZ(cx, c_addr.as_ptr());
if !js_addr.is_null() {
let av = StringValue(&*js_addr);
rooted!(&in(cx_ref) let avr = av);
JS_DefineProperty(
cx,
addr_obj.handle().into(),
c"address".as_ptr(),
avr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
args.rval().set(ObjectValue(addr_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn http_request(
cx: *mut JSContext,
argc: u32,
vp: *mut mozjs::jsval::JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let url_str = if argc > 0 {
let v = *args.get(0).ptr;
if v.is_string() {
crate::js_to_rust_string(cx, v)
} else {
String::new()
}
} else {
String::new()
};
let method = if argc > 1 {
let v = *args.get(1).ptr;
if v.is_string() {
crate::js_to_rust_string(cx, v)
} else {
"GET".to_string()
}
} else {
"GET".to_string()
};
let headers_json = if argc > 2 {
let v = *args.get(2).ptr;
if v.is_string() {
crate::js_to_rust_string(cx, v)
} else {
String::new()
}
} else {
String::new()
};
let headers_vec: Vec<(String, String)> = if !headers_json.is_empty() {
serde_json::from_str::<::std::collections::HashMap<String, String>>(&headers_json)
.unwrap_or_default()
.into_iter()
.collect()
} else {
Vec::new()
};
let body_bytes: Option<Vec<u8>> = if argc > 3 {
let v = *args.get(3).ptr;
if v.is_undefined() || v.is_null() {
None
} else if v.is_string() {
let s = crate::js_to_rust_string(cx, v);
(!s.is_empty()).then(|| s.into_bytes())
} else if v.is_object() {
match crate::node_buffer::collect_byte_view(cx, v) {
Some(bytes) => (!bytes.is_empty()).then_some(bytes),
None => {
JS_ReportErrorUTF8(
cx,
c"%s".as_ptr(),
c"http: request body must be a string, Buffer, TypedArray or ArrayBuffer".as_ptr(),
);
return false;
}
}
} else {
JS_ReportErrorUTF8(
cx,
c"%s".as_ptr(),
c"http: request body must be a string, Buffer, TypedArray or ArrayBuffer".as_ptr(),
);
return false;
}
} else {
None
};
rooted!(&in(cx_ref) let null_global = ::std::ptr::null_mut::<JSObject>());
rooted!(&in(cx_ref) let promise = unsafe {
mozjs_sys::jsapi::JS::NewPromiseObject(
cx,
null_global.handle().into(),
)
});
if promise.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let promise_obj = promise.get();
let promise_val = ObjectValue(promise_obj);
let bun_method = match method.as_str() {
"POST" => bun_http::Method::POST,
"PUT" => bun_http::Method::PUT,
"DELETE" => bun_http::Method::DELETE,
"PATCH" => bun_http::Method::PATCH,
"HEAD" => bun_http::Method::HEAD,
"OPTIONS" => bun_http::Method::OPTIONS,
_ => bun_http::Method::GET,
};
let profile: Option<bao_stealth::StealthProfile> = None;
unsafe {
crate::fetch_async::start(
cx,
promise_val,
profile,
bun_method,
url_str,
headers_vec,
body_bytes,
);
}
args.rval().set(promise_val);
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_active_servers_false_initially() {
ACTIVE_APPS.with(|s| s.borrow_mut().clear());
assert!(!has_active_servers());
}
#[test]
fn listener_fds_empty() {
let fds = listener_fds();
assert!(fds.is_empty());
}
#[test]
fn bce_007_register_unregister_flips_liveness() {
ACTIVE_APPS.with(|s| s.borrow_mut().clear());
assert!(!has_active_servers(), "no servers initially");
let sentinel_a: *mut App<false> = 0x1000 as *mut App<false>;
let sentinel_b: *mut App<false> = 0x2000 as *mut App<false>;
unsafe {
register_active_app(sentinel_a);
assert!(has_active_servers(), "registered → live");
register_active_app(sentinel_b);
assert!(has_active_servers(), "still live with two");
unregister_active_app(sentinel_a);
assert!(has_active_servers(), "still live with one");
unregister_active_app(sentinel_b);
assert!(!has_active_servers(), "all unregistered → not live");
}
}
#[test]
fn bce_007_register_is_idempotent() {
ACTIVE_APPS.with(|s| s.borrow_mut().clear());
let sentinel: *mut App<false> = 0x3000 as *mut App<false>;
unsafe {
register_active_app(sentinel);
register_active_app(sentinel);
register_active_app(sentinel);
let count = ACTIVE_APPS.with(|s| s.borrow().len());
assert_eq!(count, 1, "idempotent register must not duplicate");
unregister_active_app(sentinel);
assert!(!has_active_servers());
}
}
#[test]
fn bce_007_unregister_unknown_is_noop() {
ACTIVE_APPS.with(|s| s.borrow_mut().clear());
let sentinel: *mut App<false> = 0x4000 as *mut App<false>;
unsafe {
unregister_active_app(sentinel);
assert!(!has_active_servers(), "unregister-unknown must not panic");
register_active_app(0x5000 as *mut App<false>);
unregister_active_app(sentinel);
assert!(has_active_servers(), "unrelated unregister keeps live");
}
}
#[test]
fn bce_007_null_app_is_noop() {
ACTIVE_APPS.with(|s| s.borrow_mut().clear());
unsafe {
register_active_app(core::ptr::null_mut());
assert!(!has_active_servers(), "null register must be a no-op");
unregister_active_app(core::ptr::null_mut());
assert!(!has_active_servers(), "null unregister must be a no-op");
}
}
#[test]
fn server_user_data_keys_are_namespaced() {
let key1 = format!("http_server_{}_global", 1);
let key2 = format!("http_server_{}_handler", 1);
assert!(key1.starts_with("http_server_"));
assert!(key1.ends_with("_global"));
assert!(key2.starts_with("http_server_"));
assert!(key2.ends_with("_handler"));
assert_ne!(key1, key2, "global and handler keys must differ");
}
#[test]
fn server_user_data_next_server_id_monotonic() {
let id1 = NEXT_SERVER_ID.fetch_add(1, Ordering::Relaxed);
let id2 = NEXT_SERVER_ID.fetch_add(1, Ordering::Relaxed);
assert!(id2 > id1, "server IDs must be monotonic");
}
#[test]
fn server_user_data_global_handler_retrieve_without_cx() {
let ud = ServerUserData {
cx: ::std::ptr::null_mut(),
global_key: "http_server_999_global".to_string(),
handler_key: "http_server_999_handler".to_string(),
server_obj_key: "http_server_999_server_obj".to_string(),
};
assert!(
gc_store_get_ns(::std::ptr::null_mut(), "http", &ud.global_key).is_none(),
"gc_store_get_ns with null cx returns None"
);
assert!(
gc_store_get_ns(::std::ptr::null_mut(), "http", &ud.handler_key).is_none(),
"gc_store_get_ns with null cx returns None"
);
assert!(
ud.server_obj().is_none(),
"gc_store_get_ns with null cx returns None"
);
}
#[test]
fn server_user_data_cleanup_removes_from_gc_store() {
let ud = ServerUserData {
cx: ::std::ptr::null_mut(),
global_key: "http_server_998_global".to_string(),
handler_key: "http_server_998_handler".to_string(),
server_obj_key: "http_server_998_server_obj".to_string(),
};
ud.cleanup(); }
static STATUS_CODES: &[(&str, &str)] = &[
("100", "Continue"),
("101", "Switching Protocols"),
("102", "Processing"),
("200", "OK"),
("201", "Created"),
("202", "Accepted"),
("203", "Non-Authoritative Information"),
("204", "No Content"),
("205", "Reset Content"),
("206", "Partial Content"),
("207", "Multi-Status"),
("208", "Already Reported"),
("226", "IM Used"),
("300", "Multiple Choices"),
("301", "Moved Permanently"),
("302", "Found"),
("303", "See Other"),
("304", "Not Modified"),
("305", "Use Proxy"),
("306", "(Unused)"),
("307", "Temporary Redirect"),
("308", "Permanent Redirect"),
("400", "Bad Request"),
("401", "Unauthorized"),
("402", "Payment Required"),
("403", "Forbidden"),
("404", "Not Found"),
("405", "Method Not Allowed"),
("406", "Not Acceptable"),
("407", "Proxy Authentication Required"),
("408", "Request Timeout"),
("409", "Conflict"),
("410", "Gone"),
("411", "Length Required"),
("412", "Precondition Failed"),
("413", "Payload Too Large"),
("414", "URI Too Long"),
("415", "Unsupported Media Type"),
("416", "Range Not Satisfiable"),
("417", "Expectation Failed"),
("418", "I'm a Teapot"),
("421", "Misdirected Request"),
("422", "Unprocessable Entity"),
("423", "Locked"),
("424", "Failed Dependency"),
("425", "Too Early"),
("426", "Upgrade Required"),
("428", "Precondition Required"),
("429", "Too Many Requests"),
("431", "Request Header Fields Too Large"),
("451", "Unavailable For Legal Reasons"),
("500", "Internal Server Error"),
("501", "Not Implemented"),
("502", "Bad Gateway"),
("503", "Service Unavailable"),
("504", "Gateway Timeout"),
("505", "HTTP Version Not Supported"),
("506", "Variant Also Negotiates"),
("507", "Insufficient Storage"),
("508", "Loop Detected"),
("509", "Bandwidth Limit Exceeded"),
("510", "Not Extended"),
("511", "Network Authentication Required"),
];
#[test]
fn status_codes_count() {
assert_eq!(STATUS_CODES.len(), 63);
}
#[test]
fn status_codes_contains_200_ok() {
assert!(STATUS_CODES.iter().any(|(c, m)| *c == "200" && *m == "OK"));
}
#[test]
fn status_codes_contains_404_not_found() {
assert!(
STATUS_CODES
.iter()
.any(|(c, m)| *c == "404" && *m == "Not Found")
);
}
#[test]
fn status_codes_contains_500_internal_server_error() {
assert!(
STATUS_CODES
.iter()
.any(|(c, m)| *c == "500" && *m == "Internal Server Error")
);
}
#[test]
fn status_codes_all_numeric() {
for (code, _) in STATUS_CODES {
assert!(code.chars().all(|c| c.is_ascii_digit()));
}
}
#[test]
fn status_codes_all_non_empty_messages() {
for (_, msg) in STATUS_CODES {
assert!(!msg.is_empty());
}
}
#[test]
fn status_codes_codes_unique() {
let mut codes: Vec<&&str> = STATUS_CODES.iter().map(|(c, _)| c).collect();
codes.sort();
codes.dedup();
assert_eq!(codes.len(), STATUS_CODES.len());
}
#[test]
fn http_methods_string_format() {
let methods = "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,TRACE";
let method_list: Vec<&str> = methods.split(',').collect();
assert_eq!(method_list.len(), 8);
assert!(method_list.contains(&"GET"));
assert!(method_list.contains(&"POST"));
assert!(method_list.contains(&"DELETE"));
assert!(method_list.contains(&"PATCH"));
}
#[test]
fn http_methods_all_uppercase() {
let methods = "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,TRACE";
for m in methods.split(',') {
assert_eq!(m, m.to_uppercase());
}
}
#[test]
fn for_each_header_collects_all_headers() {
let mock_headers: Vec<(&[u8], &[u8])> = vec![
(b"host", b"example.com"),
(b"content-type", b"text/html"),
(b"x-custom", b"value"),
];
let mut collected: Vec<(&[u8], &[u8])> = Vec::new();
for (name, value) in &mock_headers {
collected.push((*name, *value));
}
assert_eq!(collected.len(), 3);
assert!(
collected
.iter()
.any(|(n, v)| *n == b"host" && *v == b"example.com")
);
assert!(
collected
.iter()
.any(|(n, v)| *n == b"x-custom" && *v == b"value")
);
}
}