ターミナル は、サンドボックスの現在のコンテナ内にある対話型 PTY です。全二重のターミナル I/O に使います。ブラウザシェル、リサイズ、割り込み、再接続ができます。
コマンド実行には exec とプロセスハンドルを使います。ターミナルは別のリソース種別です。API リファレンスは Terminals API です。
プロセス(exec) |
ターミナル | |
|---|---|---|
| 役割 | 監視付きの argv プロセス | 対話型 PTY |
| 入力 | 起動時の argv(およびプログラムが独自に読む入力) | write() またはブラウザの connect() による PTY 入力 |
| 出力 | logs()、output()、待機 |
output()、スナップショット、waitForExit() |
| 停止 | kill(signal?) |
interrupt() / terminate() |
| 参照 | getProcess / listProcesses |
getTerminal / listTerminals |
どちらのリソースも、サンドボックス ID の現在のコンテナ内にだけ存在します。参照メソッドはコンテナを起動しません。Sandbox のライフサイクル と プロセスの生存期間 を参照してください。
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();- ターミナルは、そのサンドボックス ID の 現在のコンテナ 内にだけ存在します。
- コンテナが動いていないとき、
getTerminal/listTerminalsはnull/[]を返します。コンテナは起動しません。 - コンテナが停止または置き換わると、古いターミナル ID は無効になります。再度必要なら、新しいターミナルを作成します。
- アクティブなプロセスと同様に、アクティブなターミナルは Worker リクエストをまたいでコンテナを生かし続けられます。
そのコンテナが稼働しているあいだ、同じ PTY を再開するには terminal.id を保存します。
- ターミナルを作成し、サンドボックス ID と一緒に
terminal.idを保持します。 - WebSocket のアップグレードごとに、
getTerminalでターミナルを解決し、terminal.connect(request)を返します。 - ブラウザでは、
@cloudflare/sandbox/xtermをterminalId付きで使います。
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 });
},
};UI がターミナルを必要とするときに、アプリケーションのルートから作成します。
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 });npm install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextyarn install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextpnpm install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextbun 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) |