userland.rundocs70Try the demo
Internals

Virtual server

A server inside the VM listens on a socket that has no real network behind it. The virtual server injects each browser request into that socket and reads the response back, so an iframe can talk to it like localhost.

The request path

  • The preview iframe requests /sw/<port>/<path>.
  • The service worker (nano-sw.js) intercepts it and posts the raw HTTP request, with a reply port, to the page that registered a ServeBridge.
  • The bridge calls injectConnection(port, request) on its injector (the Nano, or a worker client).
  • In the RISC-V runner, vm_inject_connection creates a connected socket pair, queues the server side on the listening socket's accept queue with the request bytes in its buffer, and wakes threads blocked in epoll_pwait.
  • The VM runs; the server accepts, reads and writes its response into the client side. The host polls vm_read_response (bytes, 0 = not yet, −1 = closed) until the response is complete.
  • parseHttpResponse turns the bytes into status, headers and body, and the service worker answers the iframe.
text
iframe ──GET /sw/8080/──▶ service worker ──postMessage──▶ page (ServeBridge)
                                                       │ injectConnection(8080, raw)
                                                       ▼
                                     VM: accept() → read → handler → write
                                                       │ vm_read_response (poll)
iframe ◀──Response── service worker ◀──postMessage──── parseHttpResponse

Exports

Export
Returns
vm_inject_connection(vm, port, req_ptr, req_len)
a connection id, or −1 when nothing listens on port
vm_read_response(vm, conn, dst, len)
bytes copied; 0 while the server is working; −1 when it closed
vm_close_connection(vm, conn)
frees the pair

Servers on the host engine

A Node.js server running on the host engine listens in the kernel's port table rather than in the RISC-V VM. Its connections are kernel pipe pairs (the same loopback nodert's own net uses). Since SDK 0.2.8, nano.injectConnection routes each request by the kernel port table: a port owned by a host-engine process gets a loopback connection (sent with Connection: close), every other port goes to the RISC-V VM. nano.listeners() lists the table. One bridge previews both kinds; the serve samples run that way.

Try the raw path

SDK · JavaScript

Inject a request without a service worker

javascript
nano.fs.writeFile("/www/index.html", "<h1>injected</h1>");
const srv = await sdk.startServer(nano, { command: "echo listening on 8085; exec busybox httpd -f -p 8085 -h /www" });
const raw = await nano.injectConnection(8085, "GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n");
print(new TextDecoder().decode(raw).split("\r\n")[0]);
const res = sdk.parseHttpResponse(raw);
print(res.status, res.headers["content-length"], new TextDecoder().decode(res.body));
srv.stop();
print(await nano.injectConnection(8085, "GET / HTTP/1.0\r\n\r\n").catch((e) => e.message));
loading…
Expected output
HTTP/1.1 200 OK
200 17 <h1>injected</h1>
inject_connection failed: -1 (no server on port 8085?)

APIs: Serve mode. Product view: dev servers.