| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- import { IncomingMessage, ServerResponse } from 'node:http';
- type JsonValue = Record<string, unknown> | Array<unknown>;
- export function sendJson(response: ServerResponse, statusCode: number, payload: JsonValue): void {
- const body = JSON.stringify(payload);
- response.writeHead(statusCode, {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'Content-Type',
- 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
- 'Content-Type': 'application/json; charset=utf-8',
- 'Content-Length': Buffer.byteLength(body),
- });
- response.end(body);
- }
- export function readBody(request: IncomingMessage): Promise<Record<string, unknown>> {
- return new Promise((resolve) => {
- let body = '';
- request.on('data', (chunk) => {
- body += chunk;
- });
- request.on('end', () => {
- if (!body) {
- resolve({});
- return;
- }
- try {
- resolve(JSON.parse(body));
- } catch {
- resolve({});
- }
- });
- });
- }
- /**
- * 从 multipart/form-data 请求中读取上传文件
- * 简易实现,支持单文件上传
- */
- export function readMultipartFile(request: IncomingMessage): Promise<{ fileName: string; buffer: Buffer }> {
- return new Promise((resolve, reject) => {
- const contentType = request.headers['content-type'] || '';
- const chunks: Buffer[] = [];
- request.on('data', (chunk: Buffer) => {
- chunks.push(chunk);
- });
- request.on('end', () => {
- const rawBuffer = Buffer.concat(chunks);
- if (contentType.includes('multipart/form-data')) {
- try {
- const result = parseMultipart(rawBuffer, contentType);
- resolve(result);
- } catch (error) {
- reject(error);
- }
- } else {
- // Treat as raw file upload with filename from header
- const disposition = request.headers['content-disposition'] || '';
- const fileNameMatch = disposition.match(/filename="?([^";\n]+)"?/);
- const fileName = fileNameMatch ? fileNameMatch[1] : 'upload.bin';
- resolve({ fileName, buffer: rawBuffer });
- }
- });
- request.on('error', reject);
- });
- }
- function parseMultipart(buffer: Buffer, contentType: string): { fileName: string; buffer: Buffer } {
- // 提取 boundary(可能带引号)
- const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
- if (!boundaryMatch) {
- throw new Error('Missing multipart boundary');
- }
- const boundary = `--${boundaryMatch[1]}`;
- const boundaryBytes = Buffer.from(boundary, 'ascii');
- // 在 buffer 中按 boundary 定位各 part
- let pos = bufferIndexOf(buffer, boundaryBytes, 0);
- if (pos === -1) {
- throw new Error('No file found in multipart data');
- }
- // 跳过第一个 boundary + CRLF
- pos += boundaryBytes.length + 2;
- while (pos < buffer.length) {
- // 找到 header/body 分隔的 \r\n\r\n
- const headerEndMarker = Buffer.from('\r\n\r\n', 'ascii');
- const headerEnd = bufferIndexOf(buffer, headerEndMarker, pos);
- if (headerEnd === -1) break;
- const headersStr = buffer.subarray(pos, headerEnd).toString('utf-8');
- const bodyStart = headerEnd + 4;
- // 找下一个 boundary
- const nextBoundary = bufferIndexOf(buffer, boundaryBytes, bodyStart);
- // body 结束于下一个 boundary 前的 \r\n
- const bodyEnd = nextBoundary === -1 ? buffer.length : nextBoundary - 2;
- // 检查是否有 filename
- const fileNameMatch = headersStr.match(/filename="?([^";\r\n]+)"?/);
- if (fileNameMatch) {
- const fileName = decodeFileName(fileNameMatch[1].trim());
- const fileBuffer = buffer.subarray(bodyStart, bodyEnd);
- return { fileName, buffer: fileBuffer };
- }
- // 跳到下一个 part
- if (nextBoundary === -1) break;
- pos = nextBoundary + boundaryBytes.length;
- // 跳过 CRLF 或 -- (结束标记)
- if (buffer[pos] === 0x2d && buffer[pos + 1] === 0x2d) break; // '--' = end
- pos += 2; // skip \r\n
- }
- throw new Error('No file found in multipart data');
- }
- function bufferIndexOf(buf: Buffer, search: Buffer, fromIndex: number): number {
- for (let i = fromIndex; i <= buf.length - search.length; i++) {
- let found = true;
- for (let j = 0; j < search.length; j++) {
- if (buf[i + j] !== search[j]) {
- found = false;
- break;
- }
- }
- if (found) return i;
- }
- return -1;
- }
- function decodeFileName(name: string): string {
- try {
- return decodeURIComponent(name);
- } catch {
- return name;
- }
- }
- /**
- * 从 URL 中提取路径参数
- * 例如: extractPathParam('/api/tasks/abc123', '/api/tasks/') => 'abc123'
- */
- export function extractPathParam(pathname: string, prefix: string): string {
- if (!pathname.startsWith(prefix)) return '';
- const rest = pathname.substring(prefix.length);
- const slashIndex = rest.indexOf('/');
- return slashIndex === -1 ? rest : rest.substring(0, slashIndex);
- }
- /**
- * 提取路径尾部
- * 例如: extractPathTail('/api/tasks/abc123/export', '/api/tasks/') => 'abc123/export'
- */
- export function extractPathTail(pathname: string, prefix: string): string {
- if (!pathname.startsWith(prefix)) return '';
- return pathname.substring(prefix.length);
- }
|