Lybic Docs

端口映射与转发

用户、开发者和智能体有时需要调试位于沙箱中的网站。由于沙箱环境的隔离特性,传统的调试方法可能无法直接应用,这时你可能需要使用端口映射等技术提供对外(对您)的远程访问能力。

当你需要将网站的服务端口(HTTP/WebSocket)映射到外部以进行调试时,你可以使用lybic的网页端口映射功能。

创建映射

创建映射会将沙箱内的,监听 本地 (LISTEN localhost:*) 的某一个指定 Web端口 (基于HTTP,支持Upgrade WebSocket协议) 映射到一个外部可访问的URL如 https://random-strs.port.lybic.cn

你可以通过以下API创建映射:

POST /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings

{
  "targetEndpoint": ""           // Target TCP endpoint, e.g., 127.0.0.1:3000
}

创建成功后,API会返回映射的URL和鉴权信息:

{
  "domain": "string",         // 访问域名
  "accessToken": "string",           // 访问该URL时需要携带的鉴权Token
  "targetEndpoint": "",   // 目标TCP端点
}

访问映射服务

为保证服务的安全性,基于我们的内容策略,用户在访问映射服务时必须经过鉴权。根据需求的不同,你可以在以下两种鉴权方案中 任选其一

请求头鉴权

只需在请求头中携带 X-Gateway-Access-Token 字段,值为映射创建时返回的 accessToken

示例:

curl -H "X-Gateway-Access-Token: <accessToken>" \
     https://<domain>

URL 参数鉴权

在访问映射的URL时,携带 x-gateway-access-token 参数,值为映射创建时返回的 accessToken。 通过 URL 参数鉴权受浏览器访问策略的限制,请参考后文。

示例:

curl "https://<domain>?x-gateway-access-token=<accessToken>"

通过浏览器访问

基于我们的内容策略,用户不能使用浏览器地址栏直接访问端口映射内容;通过 fetch 或 XHR API 访问则不在此列。

技术上而言,若你通过 URL 参数鉴权,我们会检查 User-Agent 请求头。若 User-Agent 中包含 Mozilla/5.0,且使用了 URL 参数鉴权,我们将拒绝该访问,并返回 HTTP 403 响应。

如果你需要通过浏览器地址栏直接访问端口映射的服务,建议你设置自己的反向代理服务,或通过自己的域名托管网页,并使用 Service Worker 在请求时添加请求头。此外,你也可以联系我们的商务团队,获取自定义域名支持。

Host 请求头

在转发请求时,我们会将 Host 请求头重写为 localhost,并将原本的请求头放在 X-Gateway-Host 中。

这是因为 CDP 、vite 等常用工具和软件会检查 HTTP 请求头中的 Host 请求头,确保其是 localshot; 而 Lybic 的网关服务有自己的 Access Token 安全检查,此类安全措施并不必要。

你可以设置 X-Forwarded-Host 头,网关会自动重写为 Host。这使得你可以自由地重写 Host 。例如:

curl "https://<domain>" \
  -H "X-Gateway-Access-Token: $TOKEN" \
  -H "X-Forwarded-Host: example.com"

此时转发目标收到的 HTTP 请求如下:

GET / HTTP/1.1

Host: example.com
User-Agent: curl/x.y.z
X-Forwarded-Host: example.com
X-Gateway-Host: <domain>
X-Gateway-Access-Token: <token>

删除映射

删除映射会删除指定的映射。 你可以通过以下API删除映射: DELETE /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings/<targetEndpoint>

Resp: 200 No Content

获取映射列表

你可以通过以下API获取沙箱内所有的端口映射列表: GET /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings

Resp:

[
  {
    "domain": "string",
    "targetEndpoint": "127.0.0.1:3000",
    "accessToken": "string"
  }
]

获取单个映射信息

你可以通过以下API获取单个端口映射的信息: GET /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings/<targetEndpoint>

Resp:

{
  "domain": "string",
  "targetEndpoint": "127.0.0.1:3000",
  "accessToken": "string"
}

示例

1. Playwright 启动 Chromium,暴露并映射 CDP Port URL 和 Playwright控制端口

参考 执行长期异步任务,运行以下脚本启动 Chromium:

import { chromium } from 'playwright';

(async () => {
    const server = await chromium.launchServer({
        headless: true,
        args: [
            '--remote-debugging-port=9222', // 暴露 CDP 端口
            '--no-sandbox'
        ]
    });

    const wsEndpoint = server.wsEndpoint();
    console.log('Playwright Ws endpoint:', wsEndpoint);

    const cdpEndpoint = fetch('http://localhost:9222/json/version')
        .then(res => res.json())
        .then(data => data.webSocketDebuggerUrl);
   console.log('CDP WebSocket URL:', await cdpEndpoint);
})();

Playwright Ws endpoint 是 Playwright 专用的 WebSocket URL, 形如 ws://localhost:port/<id>

CDP WebSocket URL 是使用 WebSocket 协议的,给Chrome DevTools,Puppeteer,Selenium 等工具使用的URL,形如 ws://localhost:9222/devtools/browser/<id>

使用 API 创建CDP端口映射:

curl -X POST "https://api.lybic.cn/api/orgs/<orgId>/sandboxes/<sandboxId>/mappings" \
     -H "Content-Type: application/json" \
     -d '{"targetEndpoint": "localhost:9222"}'

返回结果:

{
  "domain": "random-strs.port.lybic.cn",
  "accessToken": "link_auth_token_here",
  "targetEndpoint": "127.0.0.1:3000"
}

你可以在本地使用另一个 Node.js 进程通过 CDP 连接到该映射 URL:

import { chromium } from 'playwright';

const browser = await chromium.connectOverCDP(
  'wss://random-strs.port.lybic.cn',
  {
    headers: {
      'X-Gateway-Access-Token': 'link_auth_token_here'
    }
  }
);

const context = browser.contexts()[0];
const page = await context.newPage();

同上,如果你想使用 Playwright 连接到 Playwright 的 WebSocket URL,可以创建另一个端口映射,目标端点为 localhost:<port>,创建映射后,使用 chromium.connect(wsEndpoint+'?x-gateway-access-token=<accessToken>') 方法连接。

2. 沙箱内运行一个 Node.js 前端项目

假设你有一个简单的 Node.js 前端项目,使用 express 作为服务器,项目位于 /home/agent/frontend-app 目录下,

可以使用 npm start 启动该项目,监听本地的 3000 端口:

import { express } from 'express';
const app = express();
const port = 3000;
app.get('/', (req, res) => {
  res.send('Hello from sandboxed frontend app!');
});
app.listen(port, 'localhost', () => {
  console.log(`Frontend app listening at http://localhost:${port}`);
});

启动项目后,使用 API 创建端口映射:

curl -X POST "https://api.lybic.cn/api/orgs/<orgId>/sandboxes/<sandboxId>/mappings" \
     -H "Content-Type: application/json" \
     -d '{"targetEndpoint": "127.0.0.1:3000"}'

返回结果:

{
  "domain": "random-strs.port.lybic.cn",
  "accessToken": "link_auth_token_here",
  "targetEndpoint": "127.0.0.1:3000"
}

你可以在本地使用 curl 访问该映射 URL:

curl -H "X-Gateway-Access-Token: link_auth_token_here" https://random-strs.port.lybic.cn

SDK 使用

除了直接使用 API,您也可以使用 Lybic SDK 来管理 HTTP 端口映射。

创建端口映射

import asyncio
from lybic import LybicClient

async def main():
    async with LybicClient() as client:
        # 创建端口映射
        mapping = await client.sandbox.create_http_port_mapping(
            sandbox_id="SBX-xxxx",
            target_endpoint="127.0.0.1:3000"
        )
        print(f"映射域名: {mapping.domain}")
        print(f"访问令牌: {mapping.accessToken}")

if __name__ == '__main__':
    asyncio.run(main())
import (
    "context"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, err := lybic.NewClient(nil)
    if err != nil {
        fmt.Printf("创建客户端时出错: %v\n", err)
        return
    }
    
    // 创建端口映射
    mapping, err := client.CreateHttpPortMapping(ctx, "SBX-xxxx", "127.0.0.1:3000")
    if err != nil {
        fmt.Printf("创建端口映射时出错: %v\n", err)
        return
    }
    fmt.Printf("映射域名: %s\n", mapping.Domain)
    fmt.Printf("访问令牌: %s\n", mapping.AccessToken)
}
import { LybicClient } from '@lybic/core'

const client = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 创建端口映射
const mapping = await client.createHttpPortMapping('SBX-xxxx', {
  targetEndpoint: '127.0.0.1:3000'
})

console.log('映射域名:', mapping.data?.domain)
console.log('访问令牌:', mapping.data?.accessToken)

列出端口映射

import asyncio
from lybic import LybicClient

async def main():
    async with LybicClient() as client:
        # 列出所有端口映射
        mappings = await client.sandbox.list_http_port_mappings(
            sandbox_id="SBX-xxxx"
        )
        for mapping in mappings:
            print(f"域名: {mapping.domain}")
            print(f"目标端点: {mapping.targetEndpoint}")
            print("---")

if __name__ == '__main__':
    asyncio.run(main())
import (
    "context"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, err := lybic.NewClient(nil)
    if err != nil {
        fmt.Printf("创建客户端时出错: %v\n", err)
        return
    }
    
    // 列出所有端口映射
    mappings, err := client.ListHttpPortMappings(ctx, "SBX-xxxx")
    if err != nil {
        fmt.Printf("列出端口映射时出错: %v\n", err)
        return
    }
    
    for _, mapping := range mappings {
        fmt.Printf("域名: %s\n", mapping.Domain)
        fmt.Printf("目标端点: %s\n", mapping.TargetEndpoint)
        fmt.Println("---")
    }
}
import { LybicClient } from '@lybic/core'

const client = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 列出所有端口映射
const mappings = await client.listHttpPortMappings('SBX-xxxx')

for (const mapping of mappings.data || []) {
  console.log('域名:', mapping.domain)
  console.log('目标端点:', mapping.targetEndpoint)
  console.log('---')
}

删除端口映射

import asyncio
from lybic import LybicClient

async def main():
    async with LybicClient() as client:
        # 删除端口映射
        await client.sandbox.delete_http_port_mapping(
            sandbox_id="SBX-xxxx",
            target_endpoint="127.0.0.1:3000"
        )
        print("映射已删除")

if __name__ == '__main__':
    asyncio.run(main())
import (
    "context"
    "fmt"
    "github.com/lybic/lybic-sdk-go"
)

func main() {
    ctx := context.Background()
    client, err := lybic.NewClient(nil)
    if err != nil {
        fmt.Printf("创建客户端时出错: %v\n", err)
        return
    }
    
    // 删除端口映射
    err = client.DeleteHttpPortMapping(ctx, "SBX-xxxx", "127.0.0.1:3000")
    if err != nil {
        fmt.Printf("删除端口映射时出错: %v\n", err)
        return
    }
    fmt.Println("映射删除成功。")
}
import { LybicClient } from '@lybic/core'

const client = new LybicClient({
  baseUrl: 'https://api.lybic.cn',
  orgId: 'ORG-xxxx',
  apiKey: 'lysk-your-api-key-here',
})

// 删除端口映射
await client.deleteHttpPortMapping('SBX-xxxx', '127.0.0.1:3000')
console.log('映射删除成功。')

本页内容