151 lines
4.9 KiB
JavaScript
151 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { createServer } from 'node:http';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { extname, relative, resolve, sep } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
DEFAULT_BASE_URL,
|
|
ExternalAgentParser,
|
|
blockedResult
|
|
} from './external-agent-client.mjs';
|
|
|
|
const rootDir = resolve(import.meta.dirname);
|
|
const port = Number(process.env.PORT || 8765);
|
|
|
|
const contentTypes = {
|
|
'.css': 'text/css; charset=utf-8',
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.js': 'text/javascript; charset=utf-8',
|
|
'.json': 'application/json; charset=utf-8',
|
|
'.md': 'text/markdown; charset=utf-8',
|
|
'.mjs': 'text/javascript; charset=utf-8',
|
|
'.png': 'image/png'
|
|
};
|
|
|
|
export function createExternalParserFromEnv({ fetchImpl = globalThis.fetch } = {}) {
|
|
return new ExternalAgentParser({
|
|
baseUrl: process.env.DEERFLOW_BASE_URL || DEFAULT_BASE_URL,
|
|
apiKey: process.env.DEERFLOW_OPEN_API_KEY,
|
|
fetchImpl,
|
|
timeoutMs: parseTimeout(process.env.PARSER_REQUEST_TIMEOUT_MS),
|
|
totalTimeoutMs: parseTimeout(process.env.PARSER_TOTAL_TIMEOUT_MS || '180000')
|
|
});
|
|
}
|
|
|
|
export function createBusinessServer({ parser = createExternalParserFromEnv() } = {}) {
|
|
return createServer(async (req, res) => {
|
|
try {
|
|
const requestUrl = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
if (req.method === 'GET' && requestUrl.pathname === '/api/status') {
|
|
const aiProbe = typeof parser.checkConnection === 'function'
|
|
? await parser.checkConnection()
|
|
: {
|
|
ok: Boolean(process.env.DEERFLOW_OPEN_API_KEY),
|
|
configured: Boolean(process.env.DEERFLOW_OPEN_API_KEY),
|
|
reachable: false,
|
|
error_code: 'status_probe_unavailable'
|
|
};
|
|
sendJson(res, 200, {
|
|
ok: true,
|
|
service: 'external_parse_api',
|
|
ai_configured: Boolean(aiProbe.configured),
|
|
ai_connected: Boolean(aiProbe.ok),
|
|
ai_probe: aiProbe
|
|
});
|
|
return;
|
|
}
|
|
if (req.method === 'POST' && requestUrl.pathname === '/api/parse') {
|
|
const payload = await readRequestJson(req);
|
|
const result = await parser.parse({
|
|
rawText: payload.raw_text,
|
|
taskId: payload.task_id,
|
|
receivedAt: payload.received_at
|
|
});
|
|
sendJson(res, 200, result);
|
|
return;
|
|
}
|
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
sendJson(res, 405, { error: 'method_not_allowed' });
|
|
return;
|
|
}
|
|
await serveStatic(req, res, requestUrl);
|
|
} catch (error) {
|
|
if (error?.code === 'invalid_json') {
|
|
sendJson(res, 400, blockedResult(['请求体不是有效 JSON。'], { error_code: error.code }));
|
|
return;
|
|
}
|
|
sendJson(res, 500, {
|
|
error: 'server_error',
|
|
message: error.message || String(error)
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
export async function readRequestJson(req) {
|
|
const chunks = [];
|
|
for await (const chunk of req) chunks.push(chunk);
|
|
const body = Buffer.concat(chunks).toString('utf8');
|
|
if (!body.trim()) return {};
|
|
try {
|
|
return JSON.parse(body);
|
|
} catch (error) {
|
|
const invalidJsonError = new Error('request body must be valid JSON');
|
|
invalidJsonError.code = 'invalid_json';
|
|
throw invalidJsonError;
|
|
}
|
|
}
|
|
|
|
async function serveStatic(req, res, requestUrl) {
|
|
const pathname = decodeURIComponent(requestUrl.pathname || '/');
|
|
const requestedPath = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, '');
|
|
const filePath = resolve(rootDir, requestedPath);
|
|
const relativePath = relative(rootDir, filePath);
|
|
if (relativePath.startsWith('..') || relativePath.includes(`..${sep}`)) {
|
|
res.writeHead(403);
|
|
res.end('Forbidden');
|
|
return;
|
|
}
|
|
try {
|
|
const body = await readFile(filePath);
|
|
res.writeHead(200, {
|
|
'Content-Type': contentTypes[extname(filePath)] || 'application/octet-stream',
|
|
'Content-Length': body.byteLength
|
|
});
|
|
if (req.method === 'HEAD') res.end();
|
|
else res.end(body);
|
|
} catch (error) {
|
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
res.end('Not found');
|
|
}
|
|
}
|
|
|
|
function sendJson(res, status, data) {
|
|
const body = `${JSON.stringify(data, null, 2)}\n`;
|
|
res.writeHead(status, {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
'Content-Length': Buffer.byteLength(body)
|
|
});
|
|
res.end(body);
|
|
}
|
|
|
|
function parseTimeout(value) {
|
|
const timeout = Number(value);
|
|
return Number.isFinite(timeout) && timeout > 0 ? timeout : 120_000;
|
|
}
|
|
|
|
function isMainModule() {
|
|
return process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
}
|
|
|
|
if (isMainModule()) {
|
|
const server = createBusinessServer();
|
|
server.listen(port, '127.0.0.1', () => {
|
|
console.log(`Business system listening on http://127.0.0.1:${port}/`);
|
|
if (!process.env.DEERFLOW_OPEN_API_KEY) {
|
|
console.warn('External parsing service is not configured: set DEERFLOW_OPEN_API_KEY in the server environment.');
|
|
}
|
|
});
|
|
}
|