feat(smarts3-server): Introduce native custom S3 server implementation (Smarts3Server) with routing, middleware, context, filesystem store, controllers and XML utilities; add SmartXml and AWS SDK test; keep optional legacy s3rver backend.
This commit is contained in:
114
ts/classes/context.ts
Normal file
114
ts/classes/context.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { S3Error } from './s3-error.js';
|
||||
import { createXml } from '../utils/xml.utils.js';
|
||||
import type { FilesystemStore } from './filesystem-store.js';
|
||||
import type { Readable } from 'stream';
|
||||
|
||||
/**
|
||||
* S3 request context with helper methods
|
||||
*/
|
||||
export class S3Context {
|
||||
public method: string;
|
||||
public url: URL;
|
||||
public headers: plugins.http.IncomingHttpHeaders;
|
||||
public params: Record<string, string> = {};
|
||||
public query: Record<string, string> = {};
|
||||
public store: FilesystemStore;
|
||||
|
||||
private req: plugins.http.IncomingMessage;
|
||||
private res: plugins.http.ServerResponse;
|
||||
private statusCode: number = 200;
|
||||
private responseHeaders: Record<string, string> = {};
|
||||
|
||||
constructor(
|
||||
req: plugins.http.IncomingMessage,
|
||||
res: plugins.http.ServerResponse,
|
||||
store: FilesystemStore
|
||||
) {
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
this.store = store;
|
||||
this.method = req.method || 'GET';
|
||||
this.headers = req.headers;
|
||||
|
||||
// Parse URL and query string
|
||||
const fullUrl = `http://${req.headers.host || 'localhost'}${req.url || '/'}`;
|
||||
this.url = new URL(fullUrl);
|
||||
|
||||
// Parse query string into object
|
||||
this.url.searchParams.forEach((value, key) => {
|
||||
this.query[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set response status code
|
||||
*/
|
||||
public status(code: number): this {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set response header
|
||||
*/
|
||||
public setHeader(name: string, value: string | number): this {
|
||||
this.responseHeaders[name] = value.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send response body (string, Buffer, or Stream)
|
||||
*/
|
||||
public async send(body: string | Buffer | Readable | NodeJS.ReadableStream): Promise<void> {
|
||||
// Write status and headers
|
||||
this.res.writeHead(this.statusCode, this.responseHeaders);
|
||||
|
||||
// Handle different body types
|
||||
if (typeof body === 'string' || body instanceof Buffer) {
|
||||
this.res.end(body);
|
||||
} else if (body && typeof (body as any).pipe === 'function') {
|
||||
// It's a stream
|
||||
(body as Readable).pipe(this.res);
|
||||
} else {
|
||||
this.res.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send XML response
|
||||
*/
|
||||
public async sendXML(obj: any): Promise<void> {
|
||||
const xml = createXml(obj, { format: true });
|
||||
this.setHeader('Content-Type', 'application/xml');
|
||||
this.setHeader('Content-Length', Buffer.byteLength(xml));
|
||||
await this.send(xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an S3 error
|
||||
*/
|
||||
public throw(code: string, message: string, detail?: Record<string, any>): never {
|
||||
throw new S3Error(code, message, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse request body as string
|
||||
*/
|
||||
public async readBody(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
this.req.on('data', (chunk) => chunks.push(chunk));
|
||||
this.req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
||||
this.req.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request stream (for streaming uploads)
|
||||
*/
|
||||
public getRequestStream(): NodeJS.ReadableStream {
|
||||
return this.req;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user