示例
可复制的 JavaScript 示例,覆盖公开 HTTP 服务、远程文件、内网请求、进程、TCP 和云浏览器。
每个 JavaScript 代码块都可独立粘贴到实例的「代码」页,然后点击「运行」。远程示例需先启动客户端,将 <client-id> 替换为完整 ID,并在客户端管理中确认相应权限已应用。示例中的相对文件路径位于客户端启动目录。
发布公开 HTTP 服务
export default {
async fetch(request, _env, ctx) {
const path = new URL(request.url).pathname;
if (path === '/health') return new Response('ok\n');
ctx.waitUntil(Promise.resolve().then(() => console.log('Handled', path)));
return Response.json({ method: request.method, path });
},
};运行后在实例管理列表点击「访问」,打开公开入口。将网址路径改为 /health 后访问会返回 ok。URL 可匿名访问,应用需要鉴权时应在 handler 中实现。使用网页提供的地址,不要推导或硬编码服务域名。
env 是冻结的空对象。ctx.waitUntil() 可登记响应后的工作,但停止、替换或超时可能中断它,不保证后台工作完成。服务保持运行,直至手动停止、替换代码或达到网页中设置的最长运行时间。它与下文在客户端监听的 host.http.serve() 是两个不同的服务入口。
云浏览器导航、截图与快照
不需要安装远程客户端。浏览器能力使用本次提交的项目凭证,需要对应项目拥有浏览器权益。可为本例设置 900 秒运行上限,覆盖创建和清理所需时间:
import browser from 'lexmount:browser';
const session = await browser.create({ browserMode: 'normal' });
try {
console.log('Session:', session.sessionId);
console.log(await session.openUrl('https://example.com'));
const { targets } = await session.targets();
const page = targets.find((target) => target.type === 'page');
if (!page) throw new Error('Page target was not found');
const options = { targetId: page.id, timeoutMs: 30000 };
await session.waitSelector('h1', options);
const result = await session.waitText('Example Domain', {
...options,
selector: 'h1',
exact: true,
});
if (result.timed_out) throw new Error('Expected heading was not found');
console.log(await session.eval('document.title', options));
console.log('PNG bytes:', (await session.screenshot(options)).length);
console.log('Snapshot:', JSON.stringify(await session.snapshot(options)));
} finally {
await session.destroy();
}截图返回 PNG 字节,日志只展示长度;需要保留文件时,可在有写权限的客户端上调用 host.fs.writeFile(path, bytes)。表单交互使用 session.fill(selector, value, options) 和 session.click(selector, options),PDF 使用 session.pdf(options)。
创建的会话在 finally 中显式关闭。已有会话可通过 browser.get(sessionId) 接入,不会因任务结束自动取得清理所有权。更多参数见云浏览器接口。
发现客户端
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
for (const info of await getRemoteHosts()) {
console.log(JSON.stringify(info));
console.log('Available handle:', remoteHosts[info.id].id);
}每行主机信息包含客户端 ID、操作系统、CPU、内存等;空列表表示当前没有可用客户端。后续脚本显式选择 ID,避免误选其他电脑。
读取文件与目录
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
let count = 0;
for await (const entry of host.fs.readDir('.')) {
console.log(entry.name, entry.isDirectory);
if (++count >= 10) break;
}
const path = 'README.md'; // 替换为客户端上实际存在的文件。
console.log(await host.fs.stat(path));
console.log((await host.fs.readTextFile(path)).slice(0, 2000));输出最多 10 条目录项、文件元数据及前 2000 个文本字符。readTextFile 仍会读取整个文件,展示截断不能限制读取内存。
写入文件
先开启客户端的 fs.allow_write。本例创建专用示例目录,覆盖其中的 result.txt:
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
await host.fs.mkdir('runtime-docs-example', { recursive: true });
await host.fs.writeTextFile('runtime-docs-example/result.txt', 'Hello runtime\n');
await host.fs.writeTextFile('runtime-docs-example/result.txt', 'Second line\n', {
create: false,
append: true,
});
console.log(await host.fs.readTextFile('runtime-docs-example/result.txt'));如需二进制写入,使用 writeFile(path, new TextEncoder().encode(text))。清理示例目录可调用 remove('runtime-docs-example', { recursive: true })。
从客户端请求本机或内网服务
将 URL 替换为该客户端可达的 HTTP(S) 服务。本例假设客户端本机已有 8080 端口的 /health 接口,并为请求及正文读取设置 5 秒取消计时:
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const response = await host.fetch('http://127.0.0.1:8080/health', {
signal: controller.signal,
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
console.log(response.status, text);
} finally {
clearTimeout(timer);
controller.abort();
}这里的 127.0.0.1 指客户端电脑。切换为全局 fetch 会从云端发起,且不允许该本机地址。
查询与执行进程
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
let count = 0;
for await (const process of host.process.list()) {
console.log(process.pid, process.name);
if (++count >= 10) break;
}
const result = await host.process.exec('echo hello');
console.log(new TextDecoder().decode(result.stdout));
console.error(new TextDecoder().decode(result.stderr));
if (result.exitCode !== 0) throw new Error(`Exit code: ${result.exitCode}`);查询只需要 process.enabled,执行还需要 process.allow_exec。stdout 和 stderr 是字节,需解码;本例将完整输出缓冲后展示。
流式进程与 stdin
下例面向安装了 cat 的 Linux / macOS 客户端。并发消费两个输出流,写入后关闭 stdin,让 cat 收到 EOF 并退出:
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
const child = await host.process.spawn('cat');
try {
const [, stdout, stderr, status] = await Promise.all([
(async () => {
const writer = child.stdin.getWriter();
try {
await writer.write(new TextEncoder().encode('Hello stdin\n'));
await writer.close();
} finally {
writer.releaseLock();
}
})(),
new Response(child.stdout).text(),
new Response(child.stderr).text(),
child.status,
]);
console.log({ stdout, stderr, exitCode: status.exitCode });
if (status.exitCode !== 0) throw new Error(`Exit code: ${status.exitCode}`);
} catch (error) {
await child.kill().catch(() => {});
throw error;
}输出应包含 Hello stdin。本例用 Response.text() 收集小量输出;大输出可改用两个并发的 reader 循环,按块处理,不必将全部内容保存在内存中。
TCP 连接与监听
在客户端回环地址分配临时端口,自行连接并回显一段字节;需要 tcp.enabled。写 EOF 后继续读取回显,最后分别关闭连接和监听器:
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
const listener = await host.tcp.listen({ hostname: '127.0.0.1', port: 0 });
const serving = (async () => {
const incoming = await listener.accept();
if (!incoming) return;
try {
await incoming.readable.pipeTo(incoming.writable);
} finally {
await incoming.close();
}
})();
try {
const connection = await host.tcp.connect(listener.addr);
try {
const writer = connection.writable.getWriter();
try {
await writer.write(new TextEncoder().encode('Hello TCP\n'));
await writer.close();
} finally {
writer.releaseLock();
}
console.log(await new Response(connection.readable).text());
} finally {
await connection.close();
}
} finally {
await listener.close();
await serving;
await listener.finished;
}输出应包含 Hello TCP。连接其他服务时,将 connect 参数替换为 { hostname, port };监听地址与网络可达性由客户端所在网络决定。
在客户端监听 HTTP
本例使用空闲端口启动回环 HTTP 服务,再通过同一客户端请求它。需要 http_server.enabled;不会生成公开任务 URL。
import { getRemoteHosts, remoteHosts } from 'lexmount:remote';
await getRemoteHosts();
const host = remoteHosts['<client-id>'];
if (!host) throw new Error('Client is offline or unavailable');
const server = await host.http.serve({
hostname: '127.0.0.1',
port: 0,
fetch(request) {
return Response.json({ path: new URL(request.url).pathname });
},
});
try {
const url = `http://${server.addr.hostname}:${server.addr.port}/hello`;
const response = await host.fetch(url);
console.log(await response.json());
} finally {
await server.shutdown();
await server.finished;
}输出为 {"path":"/hello"}。需要持续提供服务时改为等待 server.finished,再从其他程序访问实际地址;结束任务时运行时会清理监听器。
