Logo of Lexmount
文档模板博客帮助中心
GitHub
Logo of LexmountLexmount Browser

开始使用

Lexmount 文档
控制台使用指南

Browser

Web Fetch

概览快速开始
常见错误

Web Search

概览快速开始搜索接口

运行时

概览快速开始接口能力示例

帮助与计费

能力名词 FAQ
更新日志联系支持

示例

可复制的 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,再从其他程序访问实际地址;结束任务时运行时会清理监听器。

接口能力

JavaScript 运行环境、远程文件与进程、HTTP/TCP、云浏览器接口及权限要求。

能力名词 FAQ

了解上下文持久化、自定义扩展、代理与文件下载等 Browser 套餐能力。

目录

发布公开 HTTP 服务
云浏览器导航、截图与快照
发现客户端
读取文件与目录
写入文件
从客户端请求本机或内网服务
查询与执行进程
流式进程与 stdin
TCP 连接与监听
在客户端监听 HTTP

问题没有解决?