Implement streaming file download for backup route

- Enhanced the backup server route to support streaming of large backup files using Node.js streams.
- Improved error handling for file access and added appropriate HTTP responses for missing parameters and inaccessible files.
- Updated response headers to include content length and type for better client handling.
This commit is contained in:
2026-04-15 17:54:28 -05:00
parent c3e50a2727
commit 50bbf7bdcc
2 changed files with 567 additions and 558 deletions

View File

@@ -1,9 +1,12 @@
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import { createReadStream } from 'node:fs';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { Readable } from 'node:stream';
import type { RequestHandler } from './$types';
// GET /backup?file=nombre.bak -> descarga el archivo físico desde BACKUP_PATH // GET /backup?file=nombre.bak -> descarga el archivo físico desde BACKUP_PATH (streaming; soporta archivos muy grandes)
export const GET = async ({ url }: { url: URL }) => { export const GET: RequestHandler = async ({ url, request }) => {
const fileName = url.searchParams.get('file'); const fileName = url.searchParams.get('file');
if (!fileName) { if (!fileName) {
return new Response('Missing file parameter', { status: 400 }); return new Response('Missing file parameter', { status: 400 });
@@ -28,14 +31,20 @@ export const GET = async ({ url }: { url: URL }) => {
console.error('Backup path is not a regular file:', filePath); console.error('Backup path is not a regular file:', filePath);
return new Response('Backup file not found or inaccessible', { status: 404 }); return new Response('Backup file not found or inaccessible', { status: 404 });
} }
console.log('Serving backup file from', filePath); console.log('Serving backup file from', filePath, 'size', st.size);
const data = await fs.readFile(filePath); const nodeStream = createReadStream(filePath, { highWaterMark: 1024 * 1024 });
request.signal.addEventListener('abort', () => {
nodeStream.destroy();
});
const body = Readable.toWeb(nodeStream) as unknown as ReadableStream<Uint8Array>;
const headers = new Headers(); const headers = new Headers();
headers.set('Content-Type', 'application/octet-stream'); headers.set('Content-Type', 'application/octet-stream');
headers.set('Content-Disposition', `attachment; filename="${fileName}"`); headers.set('Content-Disposition', `attachment; filename="${fileName}"`);
return new Response(data, { status: 200, headers }); headers.set('Content-Length', String(st.size));
} catch (e: any) { return new Response(body, { status: 200, headers });
console.error('Error serving backup file:', e?.message ?? e); } catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
console.error('Error serving backup file:', msg);
return new Response('Backup file not found or inaccessible', { status: 404 }); return new Response('Backup file not found or inaccessible', { status: 404 });
} }
}; };