← Sandbox SDK / sandbox / api
Tunnels
sandbox.tunnels jmenný prostor zpřístupňuje službu běžící uvnitř sandboxu na veřejném internetu prostřednictvím Cloudflare Tunnel. SDK spouští cloudflared uvnitř kontejneru a otevře trvalé QUIC připojení k edge síti Cloudflare.
K dispozici jsou dvě varianty:
- Quick Tunnels (
sandbox.tunnels.get(port)) : Bez nutnosti konfigurace. Cloudflare přiřadí náhodný*.trycloudflare.comhostname pro každý novýcloudflaredproces. Není potřeba účet Cloudflare, API token, DNS záznam ani vlastní doména. URL adresy se při každém restartu kontejneru změní. - Pojmenované tunely (
sandbox.tunnels.get(port, { name })) : Svažte stabilní hostname<name>.<your-zone>v zóně, kterou spravujete. Název hostitele přežije restart kontejneru a je sdílený mezi sandboxy, které požadují stejnýname. Vyžaduje Cloudflare API token, účet a zónu.
Požadavky
Obě varianty tunelu vyžadují:
- RPC transport. Volání
sandbox.tunnelsu přenosů HTTP/Websocket vyvolá"RPC transport required". Viz Konfigurace transportu.
Pojmenované tunely navíc vyžadují token Cloudflare API, účet a zónu, podrobnosti viz Pojmenované tunely: předpoklady.
Metody
tunnels.get()
Vrátí záznam tunelu pro port. SDK vytvoří nový cloudflared proces v kontejneru, pokud ještě neběží. Metoda je idempotentní: opakovaná volání se stejným (port, options) vrátí stejný záznam.
const tunnel = await sandbox.tunnels.get(
port: number,
options?: { name?: string }
): Promise<TunnelInfo>Parametry:
port: Číslo portu uvnitř sandboxu, které se má zveřejnit (1024-65535, kromě vyhrazených portů). Služba, ke které se vytváří tunel, musí již poslouchat na0.0.0.0:<port>uvnitř kontejneru.options.name(volitelné) : Jeden štítek DNS (malá písmena, číslice, vnitřní spojovníky; 1-63 znaků; žádné tečky). Po nastavení zajistí pojmenovaný tunel vázaný na<name>.<your-zone>. Pokud je vynecháno, vytvoří se quick tunnel.
Vrací: Promise<TunnelInfo> : záznam tunelu. Viz TunnelInfo.
Volání get(port) s jiným options na portu, který už má tunel, vyvolá výjimku. Zavolejte destroy(port) nejprve.
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
await sandbox.startProcess("python -m http.server 8080");
const tunnel = await sandbox.tunnels.get(8080);
console.log(tunnel.url);
// → https://random-words-here.trycloudflare.com
// Repeated calls for the same port return the same record.
const same = await sandbox.tunnels.get(8080);
console.log(same.url === tunnel.url); // true
return Response.json({ url: tunnel.url });
},
};import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
await sandbox.startProcess("python -m http.server 8080");
const tunnel = await sandbox.tunnels.get(8080);
console.log(tunnel.url);
// → https://random-words-here.trycloudflare.com
// Repeated calls for the same port return the same record.
const same = await sandbox.tunnels.get(8080);
console.log(same.url === tunnel.url); // true
return Response.json({ url: tunnel.url });
},
};tunnels.list()
Vrátí všechny tunely aktuálně sledované pro tento sandbox.
const tunnels = await sandbox.tunnels.list(): Promise<TunnelInfo[]>Vrací: Promise<TunnelInfo[]> : pole TunnelInfo záznamy. Prázdné, pokud není aktivní žádný tunel.
const tunnels = await sandbox.tunnels.list();
for (const tunnel of tunnels) {
console.log(`port ${tunnel.port} → ${tunnel.url}`);
}const tunnels = await sandbox.tunnels.list();
for (const tunnel of tunnels) {
console.log(`port ${tunnel.port} → ${tunnel.url}`);
}tunnels.destroy()
Zrušení tunelu. Přijímá buď číslo portu, nebo TunnelInfo záznam vrácený get(). Idempotentní: zrušení neznámého portu proběhne úspěšně.
await sandbox.tunnels.destroy(portOrInfo: number | TunnelInfo): Promise<void>Parametry:
portOrInfo: Buď číslo portu, neboTunnelInfozáznam vrácenýget().
const tunnel = await sandbox.tunnels.get(8080);
// Tear down by port number...
await sandbox.tunnels.destroy(8080);
// ...or by the record.
await sandbox.tunnels.destroy(tunnel);const tunnel = await sandbox.tunnels.get(8080);
// Tear down by port number...
await sandbox.tunnels.destroy(8080);
// ...or by the record.
await sandbox.tunnels.destroy(tunnel);Typy
TunnelInfo
Quick Tunnels vynechávají name; pojmenované tunely nesou popisek předaný přes options.name.
| Pole | Typ | Popis |
|---|---|---|
id |
string |
Identifikátor tunelu. quick-<random> pro rychlé tunely, respektive UUID tunelu Cloudflare Tunnel pro pojmenované tunely. |
port |
number |
Číslo portu uvnitř sandboxu, na které tunel přesměrovává provoz. |
url |
string |
Veřejná URL: https://<random>.trycloudflare.com (rychlé) nebo https://<name>.<your-zone> (s názvem). |
hostname |
string |
Součást hostname v url. |
createdAt |
string |
Časové razítko ISO-8601 udávající, kdy byl tunel vytvořen. |
name |
string |
Pouze pojmenované tunely. Popisek předaný přes options.name. U rychlých tunelů chybí. |
type TunnelInfo = QuickTunnelInfo | NamedTunnelInfo;
interface QuickTunnelInfo {
id: string;
port: number;
url: string;
hostname: string;
createdAt: string;
name?: never;
}
interface NamedTunnelInfo {
id: string;
port: number;
url: string;
hostname: string;
createdAt: string;
name: string;
}Pojmenované tunely
Pojmenované tunely váží hostname určený uživatelem: <name>.<your-zone> : postavené na spravovaném Cloudflare Tunnel a proxovaný CNAME záznam ve vaší zóně. Na rozdíl od quick tunnelů je URL adresa stabilní i po restartu kontejneru a sdílené mezi sandboxy které volají get(port, { name }) se stejným name.
Čím se liší od quick tunnels
| Aspekt | Quick tunnel | Pojmenovaný tunel |
|---|---|---|
| Hostname | Náhodný *.trycloudflare.com, přidělené společností Cloudflare |
<name>.<your-zone>, který zvolíte vy |
| Stabilita | Mění se při každém restartu kontejneru | Stabilní; zachovává se i po restartu a v průběhu celého životního cyklu sandboxu |
| účet Cloudflare | Není vyžadováno | Povinné (API token + zóna) |
| Zdroje na straně Cloudflare | Žádné | Spravovaný Cloudflare Tunnel + proxované DNS CNAME |
| Záruka dostupnosti | Žádné (pomůcka pro ladění) | Kryto standardním Cloudflare SLA vaší zóny |
| Certifikát TLS | Wildcard ve vlastnictví Cloudflare | Universal SSL na <name>.<your-zone> (pouze jeden štítek DNS) |
| Server-Sent Events | Nepodporováno (edge ukládá do vyrovnávací paměti text/event-stream) |
Podporováno |
Předpoklady
Chcete-li zřídit pojmenovaný tunel, potřebujete:
- A účet Cloudflare s zóna (doména, kterou spravujete v Cloudflare DNS).
- A Token Cloudflare API se správnými scopes.
- ID účtu a ID zóny : SDK dokáže obojí odvodit z tokenu, pokud je token omezen přesně na jedno z každého.
Vytvořte API token
Vytvořte token z My Profile > API Tokens > Create Token > Custom token s následujícími oprávněními:
| Rozsah | Použito pro |
|---|---|
| Účet · Cloudflare Tunnel · Úprava | Vytvářejte, vyhledávejte a odstraňujte tunely. |
| Zóna · DNS · Úprava | Vytvoření nebo aktualizace a odstranění proxovaného CNAME pro <name>.<your-zone>. |
| Zóna · Zóna · Čtení | Vyhledejte název zóny a odvoďte z něj <name>.<your-zone>. |
| Účet · Nastavení účtu · Čtení (volitelné) | Umožňuje SDK odvodit ID účtu z tokenu, pokud není nastaveno výslovně. |
V části Prostředky účtu, omezte rozsah tokenu na účet, kterému bude tunel patřit. V části Zone Resources, omezte jeho rozsah na konkrétní zónu, ke které jej chcete připojit.
Obojí Uživatelské API tokeny a API tokeny účtu (tajný klíč s předponou cfat_) jsou podporovány, přičemž SDK rozpozná druh tokenu a použije odpovídající introspekční koncový bod.
Vytvořte token pomocí REST API
Token můžete vytvořit i bez dashboardu. ID skupin oprávnění jsou stabilní. Ukázka níže používá zástupné hodnoty, aktuální ID získáte z GET /user/tokens/permission_groups.
curl -X POST "https://api.cloudflare.com/client/v4/user/tokens" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "sandbox-named-tunnels",
"policies": [
{
"effect": "allow",
"resources": { "com.cloudflare.api.account.<ACCOUNT_ID>": "*" },
"permission_groups": [{ "id": "<TUNNEL_EDIT_GROUP_ID>" }]
},
{
"effect": "allow",
"resources": { "com.cloudflare.api.account.zone.<ZONE_ID>": "*" },
"permission_groups": [
{ "id": "<DNS_EDIT_GROUP_ID>" },
{ "id": "<ZONE_READ_GROUP_ID>" }
]
}
]
}'Připojte token a ID k Workeru
SDK čte CLOUDFLARE_API_TOKEN z prostředí Workeru a pokusí se z tokenu automaticky odvodit ID účtu a ID zóny. Pokud je token přiřazen k více účtům nebo zónám, SDK nedokáže jednoznačně vybrat jeden z nich a musíte nastavit CLOUDFLARE_ACCOUNT_ID a/nebo CLOUDFLARE_ZONE_ID explicitně.
| Proměnná | Povinné? | Poznámky |
|---|---|---|
CLOUDFLARE_API_TOKEN |
Ano | Uložte jako tajný klíč pomocí wrangler secret put. |
CLOUDFLARE_ACCOUNT_ID |
Pouze pokud token vidí více účtů | Jinak se odvodí z tokenu. |
CLOUDFLARE_ZONE_ID |
Pouze pokud token vidí více zón | Jinak se odvodí z tokenu. |
npx wrangler secret put CLOUDFLARE_API_TOKENPro lokální vývoj umístěte proměnné do .dev.vars (vyloučeno v .gitignore). Pro produkci nastavte neutajovaná ID (pokud jsou potřeba) v části vars v konfiguraci Wrangler:
{
"vars": {
"CLOUDFLARE_ACCOUNT_ID": "<account-id>",
"CLOUDFLARE_ZONE_ID": "<zone-id>"
}
}Když se rozpoznávání nezdaří, SDK vyvolá jasnou chybu s uvedením proměnné, kterou je třeba nastavit.
Příklad
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
// Reuse an existing app process across container restarts, or start it.
let proc = await sandbox.getProcess("app");
if (!proc) {
try {
proc = await sandbox.startProcess("python -m http.server 8080", {
processId: "app",
});
} catch (err) {
if (err?.code !== "PROCESS_ALREADY_EXISTS") throw err;
proc = await sandbox.getProcess("app");
}
}
// Provision (or reuse) https://app.example.com pointing at port 8080.
const tunnel = await sandbox.tunnels.get(8080, { name: "app" });
console.log(tunnel.url); // → https://app.example.com
return Response.json({ url: tunnel.url });
},
};import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
// Reuse an existing app process across container restarts, or start it.
let proc = await sandbox.getProcess('app');
if (!proc) {
try {
proc = await sandbox.startProcess('python -m http.server 8080', { processId: 'app' });
} catch (err) {
if ((err as { code?: string })?.code !== 'PROCESS_ALREADY_EXISTS') throw err;
proc = await sandbox.getProcess('app');
}
}
// Provision (or reuse) https://app.example.com pointing at port 8080.
const tunnel = await sandbox.tunnels.get(8080, { name: "app" });
console.log(tunnel.url); // → https://app.example.com
return Response.json({ url: tunnel.url });
},
};Životní cyklus
Pojmenované tunely jsou navržené tak, aby přežily kontejner, který je vytvořil:
- První volání na
sandbox.tunnels.get(port, { name }):- Vyřeší se
<name>.<your-zone>z nakonfigurovaného ID zóny. - Vytvoří Cloudflare Tunnel prostředek s názvem
sandbox-<sandbox-id>-<name>otagovaný pomocí ID sandboxu. - Vytvoří nebo aktualizuje proxovaný
CNAMEz<name>.<your-zone>na<tunnel-id>.cfargotunnel.com. - Spouští
cloudflareduvnitř kontejneru s tokenem tunelu.
- Vyřeší se
- Následná volání se stejným
(port, name)vrátí záznam z mezipaměti, aniž by kontaktovaly Cloudflare. - Restart kontejneru (vyřazení Durable Object, nasazení, pád):
cloudflaredzanikne spolu s kontejnerem, ale Cloudflare Tunnel a DNS záznam zůstávají zachovány.- Při dalším
get(port, { name }), SDK znovu vyhledá označený tunel prostřednictvím API Cloudflare a znovu spustícloudflared. Hostname zůstává beze změny.
- Explicitní ukončení hodnotou
sandbox.tunnels.destroy(port):- Zastaví
cloudflareduvnitř kontejneru. - Odstraní prostředek Cloudflare Tunnel.
- Odstraní proxované
CNAMEzáznam.
- Zastaví
- Zničení sandboxu hodnotou
sandbox.destroy()před zastavením kontejneru zruší všechny tunely vytvořené sandboxem, včetně prostředků na straně Cloudflare.
Pokud destroy() se nepodaří dosáhnout Cloudflare API (například byl token zneplatněn mezi get() a destroy()), SDK zaznamená upozornění s názvem osiřelého tunnelId a dnsRecordId abyste mohli provést úklid ručně z dashboardu.
Zdroje Cloudflare a jejich štítkování
Pojmenované tunely vytvářejí prostředky ve vašem účtu Cloudflare, mimo kontejner sandboxu. Neukládají se do úložiště Durable Object a nezapočítávají se do kvót sandboxu, objeví se však v Cloudflare dashboardu a spotřebovávají kvóty tunelů a DNS vašeho účtu.
Pro každý (sandbox, name) dvojice SDK vytvoří:
| Prostředek | Název / umístění | Identifikátor |
|---|---|---|
| Cloudflare Tunnel | Sítě > Tunnels | sandbox-<sandbox-id>-<name> |
| Proxovaný DNS záznam | Vaše zóna, DNS > Záznamy | CNAME <name>.<zone> → <tunnel-id>.cfargotunnel.com |
Oba prostředky jsou označeny tagy, takže je můžete auditovat, dotazovat se na ně a hromadně je čistit z dashboardu nebo přes API:
- Metadata tunelu:
{ sandboxId, createdBy: 'sandbox-sdk', name, port } - Komentář k DNS záznamu:
sandbox-<sandbox-id> - Štítek prostředku (pouze pro plány Enterprise):
sandboxId:<sandbox-id>
U plánů, které nejsou Enterprise, Cloudflare odmítá tagy prostředků; SDK to rozpozná a požadavek zopakuje bez tagů. Komentář DNS a metadata tunelu se přesto použijí, takže prostředek můžete vždy dohledat zpět k jeho sandboxu.
Chcete-li vypsat všechny tunely vytvořené SDK pro daný účet:
curl "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/cfd_tunnel?name=sandbox-" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"Omezení
Obě varianty tunelu:
- Odchozí provoz přes WARP / Zero Trust. Pokud na vašem lokálním počítači běží Cloudflare WARP nebo jiná zásada odchozího provozu Zero Trust, odchozí provoz do
api.trycloudflare.coma edge síť cloudflared může být zablokována. Když k tomu dojde,tunnels.get()zůstane viset na edge handshake a nakonec vyprší časový limit. Vypněte WARP nebo přidejte egress výjimku pro tyto cíle. - Krátké zahřívání DNS. První požadavek přes zbrusu novou URL může trvat několik sekund, dokud se nepropaguje DNS, a to i po tom, co
get()se vyřeší.
Pouze Quick Tunnels:
- Adresy URL restart kontejneru nepřežijí. Cloudflare přiřadí hostname během
cloudflaredmá při startu handshake, díky němuž každý restart vede k nové URL adrese. SDK při startu kontejneru vymaže svou cache tunelů, takže dalšítunnels.get(port)vrátí nový záznam. Použijte pojmenovaný tunel pro stabilní hostname. - Bez záruky dostupnosti. Cloudflare řadí
trycloudflare.comjako pomůcku pro ladění, nikoli jako produkční cíl. - Žádné Server-Sent Events.
trycloudflare.comedge ukládá do bufferutext/event-streamodpovědi, takže události SSE se ke klientovi nikdy nedostanou. WebSockety fungují normálně. Použijte pojmenovaný tunel pokud vaše služba streamuje SSE.
Pouze pojmenované tunely:
- Jediný štítek DNS.
namenesmí obsahovat tečky. Universal SSL pokrývá pouze<name>.<your-zone>. - Započítává se do kvót vaší zóny. Každý pojmenovaný tunel vytvoří Cloudflare Tunnel a záznam DNS ve vašem účtu. Viz Limity Cloudflare Tunnel.
- Úklid vyžaduje API token. Pokud
destroy()se spustí až po zneplatnění tokenu, prostředky na straně Cloudflare zůstanou osiřelé. SDK zaznamená ID osiřelých prostředků do logu, abyste je mohli ručně odstranit.
Související zdroje
- Koncept URL adres náhledu : Náhledové URL zprostředkované Workerem a jejich rozdíly oproti quick tunnelům.
- Ports API,
exposePort()a tok preview URL zprostředkovaný Workerem. - Průvodce zpřístupněním služeb : Kompletní návod pro zveřejnění služeb v produkci.
- Konfigurace transportu : RPC vs. transport založený na trasách.