A self-contained, offline demo of host/fetch. main.go
starts a tiny HTTP server on a random loopback port, builds a VM with a print
provider and the Fetch API installed, hands the script the server's base URL, and
runs main.js.
Run it from the repository root:
go run ./examples/fetchExpected output:
GET /hello -> 200 OK
body: Hello from the local server!
GET /time -> service=demo version=1 tags=gojs,fetch
content-type: application/json
POST /echo -> 200
method seen by server: POST
api key seen by server: secret-123
echoed payload: {"hello":"world","items":[1,2,3]}
Headers demo -> accept: text/plain, application/json
Error handling -> caught TypeError: Failed to fetch: dial tcp 127.0.0.1:1: connect: connection refused
Abort demo -> caught AbortError
Done.
main.js demonstrates:
- GET + text —
await fetch(...)thenawait res.text(). - GET + JSON —
await res.json()and reading a response header. - POST JSON with custom headers — a JSON body and an
X-Api-Keyheader echoed back by the server. - Headers — case-insensitive, comma-combined values.
- Error handling — a refused connection rejects with a
TypeError. - Abort — an
AbortControllerrejects the pending fetch with anAbortError.
Because the demo server runs on 127.0.0.1, no network access is required.
Every dial made by fetch (and sse/websocket) — and therefore every DNS
lookup — passes through the VM's NetProvider, the single egress wall. This
example installs the pass-through default; wrap DialContext to enforce a
policy:
// Allowlist: the script may only reach api.mygame.com.
type allowlist struct{ d net.Dialer }
func (a allowlist) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
host, _, _ := net.SplitHostPort(addr)
if host != "api.mygame.com" {
return nil, fmt.Errorf("egress to %s denied", host)
}
return a.d.DialContext(ctx, network, addr)
}
vm := gojs.New(gojs.WithNetProvider(allowlist{}))The same seam makes network tests hermetic: a NetProvider that dials every
address to a local httptest.Server lets a script request
https://api.example.com and actually reach your loopback server — no real DNS,
no outbound sockets (see netprovider_test.go).