端口映射与转发
用户、开发者和智能体有时需要调试位于沙箱中的网站。由于沙箱环境的隔离特性,传统的调试方法可能无法直接应用,这时你可能需要使用端口映射等技术提供对外(对您)的远程访问能力。
当你需要将网站的服务端口(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.cnSDK 使用
除了直接使用 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('映射删除成功。')