Files
youtubezimu/sample/server/worker.js
T
gavindiaz 6ab8533faa
CI / build (20) (push) Canceled after 0s
CI / build (22) (push) Canceled after 0s
修改为docker部署
2026-09-08 21:17:01 +08:00

115 lines
3.2 KiB
JavaScript

import { Container, getContainer } from '@cloudflare/containers';
import { env as workerEnv } from 'cloudflare:workers';
import { Hono } from 'hono';
import { containerSlotOrderForUrl } from './worker-routing.mjs';
const app = new Hono();
const RETRYABLE_CONTAINER_STATUSES = new Set([502, 503, 504]);
export class CaptionApiContainer extends Container {
defaultPort = 8080;
sleepAfter = '30m';
enableInternet = true;
envVars = {
NODE_ENV: 'production',
OUTBOUND_PROXY_URL: workerEnv.OUTBOUND_PROXY_URL || '',
CACHE_TTL_SECONDS: workerEnv.CACHE_TTL_SECONDS || '21600',
EXTRACTION_ATTEMPTS: workerEnv.EXTRACTION_ATTEMPTS || '4',
EXTRACTION_RETRY_BASE_DELAY_MS:
workerEnv.EXTRACTION_RETRY_BASE_DELAY_MS || '300',
ALLOWED_ORIGINS: workerEnv.ALLOWED_ORIGINS || '*',
};
}
function instanceCountFor(c) {
const instanceCount = Number.parseInt(c.env.API_INSTANCE_COUNT || '2', 10);
return Number.isFinite(instanceCount) && instanceCount > 0
? instanceCount
: 1;
}
function getCaptionApiContainer(c, instanceSlot) {
const containerVersion = c.env.CONTAINER_VERSION || 'default';
return getContainer(
c.env.CAPTION_API,
`${containerVersion}-${instanceSlot}`
);
}
async function proxyToContainer(c) {
const slots = containerSlotOrderForUrl(c.req.url, instanceCountFor(c));
const requestPath = new URL(c.req.url).pathname;
let lastError;
for (let i = 0; i < slots.length; i += 1) {
try {
const container = getCaptionApiContainer(c, slots[i]);
const response = await container.fetch(new Request(c.req.raw));
if (
!requestPath.startsWith('/api/') ||
!RETRYABLE_CONTAINER_STATUSES.has(response.status) ||
i === slots.length - 1
) {
return response;
}
} catch (error) {
lastError = error;
if (i === slots.length - 1) throw error;
}
}
throw lastError || new Error('Caption API container unavailable');
}
async function requireApiToken(c, next) {
const expectedToken = c.env.CAPTION_API_TOKEN;
if (!expectedToken) {
return c.json(
{
code: 'auth_misconfigured',
message: 'CAPTION_API_TOKEN is not configured',
},
500
);
}
const authorization = c.req.header('authorization') || '';
const token = authorization.startsWith('Bearer ')
? authorization.slice('Bearer '.length).trim()
: '';
if (token !== expectedToken) {
c.header('WWW-Authenticate', 'Bearer');
return c.json({ code: 'unauthorized', message: 'Unauthorized' }, 401);
}
await next();
}
function methodNotAllowed(c) {
return c.json({ code: 'method_not_allowed', message: 'Use GET' }, 405);
}
app.get('/', (c) =>
c.json({
service: 'youtube-caption-extractor-api',
runtime: 'cloudflare-worker+hono',
backend: 'cloudflare-container+hono',
endpoints: ['/health', '/api/subtitles', '/api/videoDetails'],
})
);
app.use('/health', requireApiToken);
app.use('/api/*', requireApiToken);
app.get('/health', proxyToContainer);
app.get('/api/*', proxyToContainer);
app.all('/health', methodNotAllowed);
app.all('/api/*', methodNotAllowed);
app.notFound((c) => c.json({ code: 'not_found', message: 'Not found' }, 404));
export default app;