Appearance
通过 UNIX Domain Socket 在沙箱中运行 HTTP 服务
本文档将指导你如何通过 UNIX Domain Socket 在 Mythra Agent Studio 的沙箱中运行自己的 HTTP 服务,并让外部客户端访问你的服务。
概述
在 Mythra Agent Studio 平台上,你可以在沙箱的 Docker 容器中运行自己的 HTTP 服务。由于平台是多租户架构,所有用户的 HTTP 服务都共享同一个域名(https://agent.wiseverds.com),平台通过 URL 路径来进行路由转发,将请求正确地发送到你的服务。
两种访问方式的选择:
- UNIX Domain Socket(本文档):需要你的应用直接监听 Unix socket 文件,适用于新建服务或愿意改造服务的场景;平台通过 socket 文件路径进行路由,安全高效。
- TCP 端口(另见文档):通过容器名 + 端口号访问,适用于大多数现成的 Docker 镜像和服务(如数据库、缓存、Web 框架等),无需修改应用代码。
什么是 UNIX Domain Socket
如果你是一名 Web 开发者,可能对 TCP Socket 比较熟悉——HTTP 服务通常监听一个 TCP 端口(如 80、8080、3000 等)。而 UNIX Domain Socket 是另一种通信方式,它通过文件系统中的文件来进行通信,类似于 Windows 系统中的命名管道。
TCP Socket vs UNIX Domain Socket
| 特性 | TCP Socket | UNIX Domain Socket |
|---|---|---|
| 通信方式 | 通过 IP 地址和端口号 | 通过文件系统中的文件路径 |
| 跨机器访问 | 支持 | 不支持(仅限本机) |
| 性能 | 较低(需要网络协议栈) | 更高(直接通过内核通信) |
| 典型用途 | Web 服务、API 服务 | 本地进程间通信、Nginx 与后端服务通信 |
为什么平台使用 UNIX Domain Socket
在 Mythra Agent Studio 平台上,我们使用 UNIX Domain Socket 有以下原因:
- 安全性:UNIX Domain Socket 只能在本机访问,不会暴露到网络上
- 寻址简单:Socket 文件有明确的路径,平台可以根据路径进行路由
- 性能优势:UNIX Domain Socket 的通信效率比 TCP Socket 更高
- 无需端口管理:不需要担心端口冲突问题
平台的路由机制
当你在沙箱中运行一个监听 UNIX Domain Socket 的 HTTP 服务后,外部客户端可以通过以下 URL 格式访问:
https://agent.wiseverds.com/WebIDE/api/v1/projects/{ProjectID}/sandboxes/{SandboxID}/unix-socket/{SockPathB64}/http/其中:
ProjectID:你的项目 IDSandboxID:你的沙箱 IDSockPathB64:Socket 文件路径的 Base64 编码
平台会将这个 URL 的请求转发到沙箱中对应的 Socket 文件。
快速开始
让我们从一个最简单的例子开始:创建一个 "Hello World" HTTP 服务。
Node.js 示例
创建一个名为 server.js 的文件:
javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
// 定义 socket 文件路径
const socketPath = path.join(__dirname, 'app.sock');
// 启动前先删除旧的 socket 文件(避免地址已被占用错误)
if (fs.existsSync(socketPath)) {
fs.unlinkSync(socketPath);
}
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from UNIX Domain Socket!\n');
});
// 创建 HTTP 服务器
server.listen(socketPath, () => {
console.log(`HTTP server listening on socket: ${socketPath}`);
// 给 socket 文件加上权限,让其他进程能访问
fs.chmodSync(socketPath, '0777');
});Python 示例
创建一个名为 server.py 的文件:
python
import http.server
import socketserver
import os
import socket
import base64
# 定义 socket 文件路径
socket_path = os.path.join(os.path.dirname(__file__), 'app.sock')
# 启动前先删除旧的 socket 文件(避免地址已被占用错误)
if os.path.exists(socket_path):
os.unlink(socket_path)
class UnixSocketHTTPServer(socketserver.UnixStreamServer):
allow_reuse_address = True
def __init__(self, socket_path, handler):
self.socket_path = socket_path
super().__init__(socket_path, handler)
# 创建 HTTP 请求处理类
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'Hello from UNIX Domain Socket!\n')
with UnixSocketHTTPServer(socket_path, Handler) as httpd:
# 给 socket 文件加上权限,让其他进程能访问
os.chmod(socket_path, 0o777)
print(f'HTTP server listening on socket: {socket_path}')
# 保持服务运行
httpd.serve_forever()启动服务
在沙箱中运行你的服务:
bash
# Node.js
node server.js
# 或 Python
python3 server.py服务启动后,你会在控制台看到类似输出:
HTTP server listening on socket: /path/to/app.sock此时,你的 HTTP 服务已经在沙箱中监听了 app.sock 这个 UNIX Domain Socket,你应当可以在沙箱的文件管理器中看到 app.sock 文件。
root@8052aa1f3bb3:/data# ls -l app.sock
srwxrwxrwx 1 root root 0 May 28 15:21 app.sock访问你的 HTTP 服务
获取访问 URL
要访问你的 HTTP 服务,你需要构造正确的 URL。假设:
- 项目 ID:
2231 - 沙箱 ID:
j4urFhRG - Socket 文件路径:
app.sock(相对路径) - 接口路径: 你的HTTP接口的路径,例如
/user/login
首先,将 Socket 文件路径转换为 Base64 编码:
bash
# 在沙箱中执行
echo -n "app.sock" | base64
# 或者用 Node.js 进行 Base64 编码
node -e "console.log(Buffer.from('app.sock').toString('base64'))"
# 输出:YXBwLnNvY2s=然后构造完整的 URL:
https://agent.wiseverds.com/WebIDE/api/v1/projects/2231/sandboxes/j4urFhRG/unix-socket/YXBwLnNvY2s=/http/user/login此外,为了保证接口安全,平台要求在请求头中包含有效的 API 密钥。请确保在 HTTP 请求的 Authorization 头中添加正确的 API 密钥,使用 Bearer 前缀。
使用 curl 测试
bash
#!/bin/bash
WDS_BASE_URL="https://agent.wiseverds.com"
WDS_API_KEY="WDS_**************"
ProjectID=2231
SandboxID=j4urFhRG
SockPath=app.sock
SockPathB64=$(echo -n "$SockPath" | base64)
curl -i \
-H "Authorization: Bearer $WDS_API_KEY" \
"$WDS_BASE_URL/WebIDE/api/v1/projects/$ProjectID/sandboxes/$SandboxID/unix-socket/$SockPathB64/http/"如果一切正常,你会看到:
HTTP/1.1 200 OK
Content-Type: text/plain
Hello from UNIX Domain Socket!进阶用法
将现有的 TCP 服务转换为 UNIX Socket
如果你已经有一个监听 TCP 端口的 HTTP 服务(例如监听 8080 端口),可以使用沙箱中提供的 socat 工具将其转换为监听 UNIX Domain Socket:
bash
# 假设你的服务监听在 localhost:8080
socat UNIX-LISTEN:app.sock,fork,reuseaddr TCP:localhost:8080这个命令会:
- 监听
app.sock这个 UNIX Domain Socket - 将所有收到的请求转发到
localhost:8080 fork参数允许处理多个并发连接reuseaddr参数允许重用 Socket 文件
使用 Express 框架(Node.js)
如果你使用 Express 框架,同样可以监听 UNIX Domain Socket:
javascript
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const socketPath = path.join(__dirname, 'app.sock');
if (fs.existsSync(socketPath)) {
fs.unlinkSync(socketPath);
}
app.get('/', (req, res) => {
res.send('Hello from Express on UNIX Domain Socket!');
});
app.listen(socketPath, () => {
console.log(`Express server listening on socket: ${socketPath}`);
fs.chmodSync(socketPath, '0777');
});使用 Flask 框架(Python)
如果你使用 Flask 框架,可以通过 gevent 或 uwsgi 来监听 UNIX Domain Socket:
python
from flask import Flask
import os
import socket
app = Flask(__name__)
socket_path = os.path.join(os.path.dirname(__file__), 'app.sock')
@app.route('/')
def hello():
return 'Hello from Flask on UNIX Domain Socket!'
if __name__ == '__main__':
if os.path.exists(socket_path):
os.unlink(socket_path)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(socket_path)
os.chmod(socket_path, 0o777)
sock.listen(1)
print(f'Flask server listening on socket: {socket_path}')
app.run(sock=sock)处理路径和查询参数
平台在转发请求时,会自动进行 URL rewrite,将请求路径中的 .../http/ 前缀去掉,仅保留自定义接口路径。
因此,你的 HTTP 服务可以按照正常逻辑处理接口路径和查询参数:
javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
const socketPath = path.join(__dirname, 'app.sock');
if (fs.existsSync(socketPath)) {
fs.unlinkSync(socketPath);
}
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
console.log('Path:', parsedUrl.pathname);
console.log('Query:', parsedUrl.query);
if (parsedUrl.pathname === '/api/users') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ users: ['Alice', 'Bob'] }));
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(socketPath, () => {
console.log(`HTTP server listening on socket: ${socketPath}`);
fs.chmodSync(socketPath, '0777');
});访问示例:
bash
curl -H "Authorization: Bearer $WDS_API_KEY" \
"$WDS_BASE_URL/WebIDE/api/v1/projects/$ProjectID/sandboxes/$SandboxID/unix-socket/$SockPathB64/http/api/users?page=1"使用 WebSocket 协议
平台不仅支持 HTTP 协议,还支持 WebSocket 协议,可用于开发实时应用。
平台对于 WebSocket 请求的路由规则与 HTTP 完全相同,平台会自动判断请求是 HTTP 还是 WebSocket,并进行相应的转发。
Node.js WebSocket 示例
以下是一个简单的 WebSocket echo 服务示例:
javascript
const fs = require('fs');
const path = require('path');
const http = require('http');
const WebSocket = require('ws'); // npm install ws
const socketPath = path.join(__dirname, 'app.sock');
if (fs.existsSync(socketPath)) {
fs.unlinkSync(socketPath);
}
// 1. 先建一个普通 http server,监听 unix socket
const server = http.createServer();
server.listen(socketPath, () => {
console.log('HTTP server listening on', socketPath);
fs.chmodSync(socketPath, '0777');
});
// 2. 再在这个 http server 上建一个 websocket server
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log('Received:', message);
ws.send(`Echo: ${message}`);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});Python WebSocket 示例
使用 websockets 库实现 WebSocket echo 服务:
python
import asyncio
import websockets # pip install websockets 或者 uv add websockets
import os
socket_path = os.path.join(os.path.dirname(__file__), 'app.sock')
if os.path.exists(socket_path):
os.unlink(socket_path)
async def echo(websocket):
print('Client connected')
try:
async for message in websocket:
print(f'Received: {message}')
await websocket.send(f'Echo: {message}')
except Exception as e:
print('Exception:', e)
print('Client disconnected')
async def main():
async with websockets.unix_serve(echo, socket_path):
os.chmod(socket_path, 0o777)
print(f'WebSocket server listening on socket: {socket_path}')
await asyncio.Future()
if __name__ == '__main__':
asyncio.run(main())使用 wscat 测试
首先安装 wscat:
bash
npm install -g wscat然后连接到你的 WebSocket 服务:
bash
#!/bin/bash
WDS_BASE_URL="https://agent.wiseverds.com"
WDS_API_KEY="WDS_**************"
ProjectID=2231
SandboxID=j4urFhRG
SockPath=app.sock
SockPathB64=$(echo -n "$SockPath" | base64)
wscat -c "wss://$WDS_BASE_URL/WebIDE/api/v1/projects/$ProjectID/sandboxes/$SandboxID/unix-socket/$SockPathB64/http/" \
-H "Authorization: Bearer $WDS_API_KEY"连接成功后,你可以输入任意消息,服务器会返回 Echo: <你的消息>。
注意:
- WebSocket 使用
wss://协议(加密的 WebSocket) - URL 路径格式与 HTTP 完全相同
- 同样需要提供
Authorization头部进行身份验证
常见问题
Socket 文件已存在错误
如果启动服务时遇到 EADDRINUSE 错误,说明 Socket 文件已存在。解决方法是在启动前删除旧的 Socket 文件:
javascript
// Node.js
if (fs.existsSync(socketPath)) {
fs.unlinkSync(socketPath);
}python
# Python
if os.path.exists(socket_path):
os.unlink(socket_path)权限问题
如果遇到权限错误,确保 Socket 文件的权限设置为 0777:
bash
# 在启动服务后执行
chmod 0777 app.sock或在代码中设置:
javascript
// Node.js
fs.chmodSync(socketPath, '0777');python
# Python
os.chmod(socket_path, 0o777)如何获取项目 ID 和沙箱 ID
你可以在 Mythra Agent Studio 的界面中查看项目 ID 和沙箱 ID,或者通过 API 获取。
Socket 文件路径的 Base64 编码
确保使用正确的 Base64 编码方式:
bash
# 正确的方式(不包含换行符)
echo -n "app.sock" | base64
# 错误的方式(包含换行符)
echo "app.sock" | base64你也可以使用 Node.js 进行 Base64 编码:
javascript
// Node.js
console.log(Buffer.from('app.sock').toString('base64'));
// 输出:YXBwLnNvY2s=或者使用 Python 进行 Base64 编码:
python
# Python
import base64
socket_path = 'app.sock'
base64_path = base64.b64encode(socket_path.encode('utf-8')).decode('utf-8')
print(base64_path)
# 输出:YXBwLnNvY2s=服务无法访问
如果服务无法访问,请检查:
- 你的 HTTP 服务是否正在运行
- Socket 文件是否存在
- Socket 文件权限是否正确
- URL 中的 Base64 编码是否正确
- API Key 是否有效
最佳实践
- 清理 Socket 文件:在启动服务前,总是删除可能存在的旧 Socket 文件
- 设置正确的权限:确保 Socket 文件权限为
0777,让平台可以访问 - 使用绝对路径:建议使用绝对路径指定 Socket 文件位置,避免路径混淆
- 日志记录:记录请求日志,便于调试和监控
- 错误处理:妥善处理各种错误情况,如 Socket 文件创建失败、权限问题等
- 优雅关闭:在服务关闭时清理 Socket 文件
相关文档
- 通过 TCP 端口访问容器内服务 - 另一种访问容器内服务的方式,无需修改应用代码
- 沙箱 - 了解沙箱的基本概念和使用方法
- 开发外部 AI Agent - 学习如何开发外部 Agent 服务