Skip to content

通过 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 SocketUNIX Domain Socket
通信方式通过 IP 地址和端口号通过文件系统中的文件路径
跨机器访问支持不支持(仅限本机)
性能较低(需要网络协议栈)更高(直接通过内核通信)
典型用途Web 服务、API 服务本地进程间通信、Nginx 与后端服务通信

为什么平台使用 UNIX Domain Socket ​

在 Mythra Agent Studio 平台上,我们使用 UNIX Domain Socket 有以下原因:

  1. 安全性:UNIX Domain Socket 只能在本机访问,不会暴露到网络上
  2. 寻址简单:Socket 文件有明确的路径,平台可以根据路径进行路由
  3. 性能优势:UNIX Domain Socket 的通信效率比 TCP Socket 更高
  4. 无需端口管理:不需要担心端口冲突问题

平台的路由机制 ​

当你在沙箱中运行一个监听 UNIX Domain Socket 的 HTTP 服务后,外部客户端可以通过以下 URL 格式访问:

https://agent.wiseverds.com/WebIDE/api/v1/projects/{ProjectID}/sandboxes/{SandboxID}/unix-socket/{SockPathB64}/http/

其中:

  • ProjectID:你的项目 ID
  • SandboxID:你的沙箱 ID
  • SockPathB64: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

这个命令会:

  1. 监听 app.sock 这个 UNIX Domain Socket
  2. 将所有收到的请求转发到 localhost:8080
  3. fork 参数允许处理多个并发连接
  4. 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=

服务无法访问 ​

如果服务无法访问,请检查:

  1. 你的 HTTP 服务是否正在运行
  2. Socket 文件是否存在
  3. Socket 文件权限是否正确
  4. URL 中的 Base64 编码是否正确
  5. API Key 是否有效

最佳实践 ​

  1. 清理 Socket 文件:在启动服务前,总是删除可能存在的旧 Socket 文件
  2. 设置正确的权限:确保 Socket 文件权限为 0777,让平台可以访问
  3. 使用绝对路径:建议使用绝对路径指定 Socket 文件位置,避免路径混淆
  4. 日志记录:记录请求日志,便于调试和监控
  5. 错误处理:妥善处理各种错误情况,如 Socket 文件创建失败、权限问题等
  6. 优雅关闭:在服务关闭时清理 Socket 文件

相关文档 ​

Happy Coding!