← Sandbox SDK / sandbox / 1-0-preview
Терминалы
A терминал это интерактивный PTY в текущем контейнере песочницы. Используйте его для полнодуплексного ввода-вывода терминала: оболочка в браузере, изменение размера, прерывание и переподключение.
Выполнение команд использует exec и process handle. Terminals представляют собой отдельный тип ресурса. Справочник API: Terminals API.
Процессы и терминалы
Процесс (exec) |
Терминал | |
|---|---|---|
| Роль | Управляемый процесс argv | Интерактивный PTY |
| Ввод | argv при запуске (и всё, что программа считывает самостоятельно) | Ввод PTY через write() или браузер connect() |
| Вывод | logs(), output(), ожидает |
output(), снимок, waitForExit() |
| Остановка | kill(signal?) |
interrupt() / terminate() |
| Поиск | getProcess / listProcesses |
getTerminal / listTerminals |
Оба типа ресурсов существуют только в текущем контейнере для данного ID песочницы. Методы поиска не запускают контейнер. См. Жизненный цикл песочницы и Сколько живёт процесс.
Создайте терминал
const terminal = await sandbox.createTerminal({
command: ["bash"],
cwd: "/workspace",
cols: 120,
rows: 40,
});
console.log(terminal.id);const terminal = await sandbox.createTerminal({
command: ["bash"],
cwd: "/workspace",
cols: 120,
rows: 40,
});
console.log(terminal.id);Из Worker можно записывать данные в PTY, изменять его размер, передавать вывод потоком или завершать его:
await terminal.write(new TextEncoder().encode("uname -a\n"));
await terminal.resize(100, 30);
await terminal.terminate();await terminal.write(new TextEncoder().encode("uname -a\n"));
await terminal.resize(100, 30);
await terminal.terminate();Lifetime
- Терминал существует только в текущий контейнер для этого ID сандбокса.
getTerminal/listTerminalsвозвращатьnull/[]когда контейнер не запущен. Они не запускают новый.- После остановки или замены контейнера старые идентификаторы терминалов становятся недействительными. Если терминал нужен снова, создайте новый.
- Активный терминал может поддерживать контейнер в рабочем состоянии между запросами Worker, как и активный процесс.
Сохраните terminal.id чтобы возобновить работу с тем же PTY, пока этот контейнер ещё запущен.
Подключение к браузеру
- Создайте терминал и сохраните
terminal.idс идентификатором песочницы. - При каждом обновлении до WebSocket разрешайте терминал с помощью
getTerminal, затем вернитеterminal.connect(request). - В браузере используйте
@cloudflare/sandbox/xtermсterminalId.
Worker
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (
url.pathname === "/ws/terminal" &&
request.headers.get("Upgrade")?.toLowerCase() === "websocket"
) {
const sandboxId = url.searchParams.get("sandboxId");
const terminalId = url.searchParams.get("terminalId");
if (!sandboxId || !terminalId) {
return new Response("sandboxId and terminalId are required", {
status: 400,
});
}
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.getTerminal(terminalId);
if (!terminal) {
return new Response("Terminal not found", { status: 404 });
}
return terminal.connect(request, {
cursor: url.searchParams.get("cursor") ?? undefined,
});
}
return new Response("Not found", { status: 404 });
},
};import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (
url.pathname === "/ws/terminal" &&
request.headers.get("Upgrade")?.toLowerCase() === "websocket"
) {
const sandboxId = url.searchParams.get("sandboxId");
const terminalId = url.searchParams.get("terminalId");
if (!sandboxId || !terminalId) {
return new Response("sandboxId and terminalId are required", {
status: 400,
});
}
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.getTerminal(terminalId);
if (!terminal) {
return new Response("Terminal not found", { status: 404 });
}
return terminal.connect(request, {
cursor: url.searchParams.get("cursor") ?? undefined,
});
}
return new Response("Not found", { status: 404 });
},
};Создайте терминал из маршрута приложения, когда он нужен интерфейсу:
const sandboxId = "user-123";
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.createTerminal({ command: ["bash"] });
return Response.json({ sandboxId, terminalId: terminal.id });const sandboxId = "user-123";
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.createTerminal({ command: ["bash"] });
return Response.json({ sandboxId, terminalId: terminal.id });Браузер (xterm.js)
npm install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextimport { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { SandboxAddon } from "@cloudflare/sandbox/xterm";
import "@xterm/xterm/css/xterm.css";
const term = new Terminal({ cursorBlink: true });
const fitAddon = new FitAddon();
const sandboxAddon = new SandboxAddon({
// `origin` is already a WebSocket origin (`wss://` or `ws://`).
getWebSocketUrl: ({ sandboxId, terminalId, cursor, origin }) => {
const params = new URLSearchParams({ sandboxId });
if (terminalId) params.set("terminalId", terminalId);
if (cursor) params.set("cursor", cursor);
return `${origin}/ws/terminal?${params}`;
},
reconnect: true,
});
term.loadAddon(fitAddon);
term.loadAddon(sandboxAddon);
term.open(document.getElementById("terminal"));
fitAddon.fit();
// Values returned by your create-terminal route
const sandboxId = "user-123";
const terminalId = "term_...";
sandboxAddon.connect({ sandboxId, terminalId });import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { SandboxAddon } from "@cloudflare/sandbox/xterm";
import "@xterm/xterm/css/xterm.css";
const term = new Terminal({ cursorBlink: true });
const fitAddon = new FitAddon();
const sandboxAddon = new SandboxAddon({
// `origin` is already a WebSocket origin (`wss://` or `ws://`).
getWebSocketUrl: ({ sandboxId, terminalId, cursor, origin }) => {
const params = new URLSearchParams({ sandboxId });
if (terminalId) params.set("terminalId", terminalId);
if (cursor) params.set("cursor", cursor);
return `${origin}/ws/terminal?${params}`;
},
reconnect: true,
});
term.loadAddon(fitAddon);
term.loadAddon(sandboxAddon);
term.open(document.getElementById("terminal")!);
fitAddon.fit();
// Values returned by your create-terminal route
const sandboxId = "user-123";
const terminalId = "term_...";
sandboxAddon.connect({ sandboxId, terminalId });| Стабильный пакет | Предпросмотр |
|---|---|
sandbox.terminal(request) |
createTerminal + getTerminal + connect |
xterm / URL sessionId |
terminalId (и необязательный cursor) |