diff --git a/apps/pdftk/src/main.ts b/apps/pdftk/src/main.ts index b99f3e98..214bcdac 100644 --- a/apps/pdftk/src/main.ts +++ b/apps/pdftk/src/main.ts @@ -20,26 +20,68 @@ const PORT = process.env.PORT || '3000'; httpServer( connect( post({ path: '/compress' }, async ({ req, res }) => { - await compressStream({ input: req, output: res }); + try { + await compressStream({ input: req, output: res }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }), post({ path: '/uncompress' }, async ({ req, res }) => { - await uncompressStream({ input: req, output: res }); + try { + await uncompressStream({ input: req, output: res }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }), post( { path: '/encrypt' }, middlewareQuery(encryptSchema), async ({ req, res, query: { password, userPassword, allow } }) => { - await encryptStream({ input: req, output: res, password, userPassword, allow }); + try { + await encryptStream({ input: req, output: res, password, userPassword, allow }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }, ), post({ path: '/decrypt' }, middlewareQuery(decryptSchema), async ({ req, res, query: { password } }) => { - await decryptStream({ input: req, output: res, password }); + try { + await decryptStream({ input: req, output: res, password }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }), post({ path: '/data/fields' }, async ({ req, res }) => { - await dataFieldsStream({ input: req, output: res }); + try { + await dataFieldsStream({ input: req, output: res }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }), post({ path: '/data/dump' }, async ({ req, res }) => { - await dataDumpStream({ input: req, output: res }); + try { + await dataDumpStream({ input: req, output: res }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }), // post({ path: '/data/annots' }, async ({ req, res }) => { // const pdftkSpawn = spawn('java', ['-jar', '/pdftk/pdftk.jar', '-', 'dump_data_annots', '-']); @@ -57,11 +99,25 @@ httpServer( { path: '/form/fill' }, middlewareQuery(formFillSchema), async ({ req, res, query: { flag, fontName, ...data } }) => { - await formFillStream({ input: req, output: res, flag, fontName, data }); + try { + await formFillStream({ input: req, output: res, flag, fontName, data }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }, ), post({ path: '/data/fdf' }, async ({ req, res }) => { - await dataFdfStream({ input: req, output: res }); + try { + await dataFdfStream({ input: req, output: res }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'pdftk failed'); + } + } }), ...healthEndpoints, ), diff --git a/apps/tesseract/src/main.ts b/apps/tesseract/src/main.ts index 4e1c034d..67a84e40 100644 --- a/apps/tesseract/src/main.ts +++ b/apps/tesseract/src/main.ts @@ -8,7 +8,14 @@ httpServer( connect( post({ path: '/image-to-text' }, async ({ req, res }) => { res.setHeader('Content-Type', 'text/plain'); - imageToText({ input: req, output: res }); + try { + await imageToText({ input: req, output: res }); + } catch (error) { + if (!res.headersSent) { + res.statusCode = 500; + res.end(error instanceof Error ? error.message : 'tesseract failed'); + } + } }), ...healthEndpoints, ), diff --git a/libs/binary/tesseract/src/lib/image-to-text.ts b/libs/binary/tesseract/src/lib/image-to-text.ts index 93c94cd9..9ccdc984 100644 --- a/libs/binary/tesseract/src/lib/image-to-text.ts +++ b/libs/binary/tesseract/src/lib/image-to-text.ts @@ -2,7 +2,7 @@ import { spawn } from 'node:child_process'; import type { Writable } from 'node:stream'; import { type InputType, streamChildProcess, streamChildProcessToBuffer } from '@container/stream'; -export function imageToText(options: { input: InputType; output: Writable }): void; +export function imageToText(options: { input: InputType; output: Writable }): Promise; export function imageToText(options: { input: InputType }): Promise; export function imageToText({ input, output }: { input: InputType; output?: Writable }) { const tesseract = spawn('tesseract', ['-', '-']); diff --git a/libs/stream/src/lib/stream-child-process.ts b/libs/stream/src/lib/stream-child-process.ts index 64bc691e..a08a2f8a 100644 --- a/libs/stream/src/lib/stream-child-process.ts +++ b/libs/stream/src/lib/stream-child-process.ts @@ -31,11 +31,31 @@ export async function streamChildProcess( options?: StreamChildProcessOptions, ): Promise { const { end = true } = options ?? {}; + const stderrChunks: Buffer[] = []; + child.stderr.on('data', (chunk) => { + stderrChunks.push(Buffer.from(chunk)); + }); + + child.stderr.on('error', (error) => { + console.error(error); + }); + child.stdin.on('error', (error) => { console.error(error); + child.kill(); }); - await streamInputToWriteable(input, child.stdin, { end: true }); + try { + await streamInputToWriteable(input, child.stdin, { end: true }); + } catch (error) { + child.kill(); + const stderrOutput = Buffer.concat(stderrChunks).toString('utf-8'); + if (stderrOutput) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`${message}: ${stderrOutput}`); + } + throw error; + } child.stdout .on('error', (error) => { @@ -43,33 +63,32 @@ export async function streamChildProcess( }) .pipe(output, { end }); - const stderrChunks: Buffer[] = []; - child.stderr.on('data', (chunk) => { - stderrChunks.push(Buffer.from(chunk)); - }); - - child.stderr.on('error', (error) => { - console.error(error); - }); + let stdoutFinished = false; output.on('close', () => { - child.kill(); + if (!stdoutFinished) { + child.kill(); + } }); // Set up exit listener before awaiting to avoid race condition - const exitPromise = new Promise((resolve) => { - child.on('exit', (code) => { - resolve(code); + const exitPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve) => { + child.on('exit', (code, signal) => { + resolve({ code, signal }); }); }); await finished(child.stdout); + stdoutFinished = true; // Wait for the process to exit and check the exit code - const exitCode = await exitPromise; + const { code, signal } = await exitPromise; - if (exitCode !== 0) { + if (code !== 0) { const stderrOutput = Buffer.concat(stderrChunks).toString('utf-8'); - throw new Error(`Child process exited with code ${exitCode}${stderrOutput ? `: ${stderrOutput}` : ''}`); + if (code === null && signal) { + throw new Error(`Child process exited with signal ${signal}${stderrOutput ? `: ${stderrOutput}` : ''}`); + } + throw new Error(`Child process exited with code ${code}${stderrOutput ? `: ${stderrOutput}` : ''}`); } }