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:
130
ts/controllers/bucket.controller.ts
Normal file
130
ts/controllers/bucket.controller.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { S3Context } from '../classes/context.js';
|
||||
|
||||
/**
|
||||
* Bucket-level operations
|
||||
*/
|
||||
export class BucketController {
|
||||
/**
|
||||
* HEAD /:bucket - Check if bucket exists
|
||||
*/
|
||||
public static async headBucket(
|
||||
req: plugins.http.IncomingMessage,
|
||||
res: plugins.http.ServerResponse,
|
||||
ctx: S3Context,
|
||||
params: Record<string, string>
|
||||
): Promise<void> {
|
||||
const { bucket } = params;
|
||||
|
||||
if (await ctx.store.bucketExists(bucket)) {
|
||||
ctx.status(200).send('');
|
||||
} else {
|
||||
ctx.throw('NoSuchBucket', 'The specified bucket does not exist');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /:bucket - Create bucket
|
||||
*/
|
||||
public static async createBucket(
|
||||
req: plugins.http.IncomingMessage,
|
||||
res: plugins.http.ServerResponse,
|
||||
ctx: S3Context,
|
||||
params: Record<string, string>
|
||||
): Promise<void> {
|
||||
const { bucket } = params;
|
||||
|
||||
await ctx.store.createBucket(bucket);
|
||||
ctx.status(200).send('');
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /:bucket - Delete bucket
|
||||
*/
|
||||
public static async deleteBucket(
|
||||
req: plugins.http.IncomingMessage,
|
||||
res: plugins.http.ServerResponse,
|
||||
ctx: S3Context,
|
||||
params: Record<string, string>
|
||||
): Promise<void> {
|
||||
const { bucket } = params;
|
||||
|
||||
await ctx.store.deleteBucket(bucket);
|
||||
ctx.status(204).send('');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /:bucket - List objects
|
||||
* Supports both V1 and V2 listing (V2 uses list-type=2 query param)
|
||||
*/
|
||||
public static async listObjects(
|
||||
req: plugins.http.IncomingMessage,
|
||||
res: plugins.http.ServerResponse,
|
||||
ctx: S3Context,
|
||||
params: Record<string, string>
|
||||
): Promise<void> {
|
||||
const { bucket } = params;
|
||||
const isV2 = ctx.query['list-type'] === '2';
|
||||
|
||||
const result = await ctx.store.listObjects(bucket, {
|
||||
prefix: ctx.query.prefix,
|
||||
delimiter: ctx.query.delimiter,
|
||||
maxKeys: ctx.query['max-keys'] ? parseInt(ctx.query['max-keys']) : 1000,
|
||||
continuationToken: ctx.query['continuation-token'],
|
||||
});
|
||||
|
||||
if (isV2) {
|
||||
// List Objects V2 response
|
||||
await ctx.sendXML({
|
||||
ListBucketResult: {
|
||||
'@_xmlns': 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
Name: bucket,
|
||||
Prefix: result.prefix || '',
|
||||
MaxKeys: result.maxKeys,
|
||||
KeyCount: result.contents.length,
|
||||
IsTruncated: result.isTruncated,
|
||||
...(result.delimiter && { Delimiter: result.delimiter }),
|
||||
...(result.nextContinuationToken && {
|
||||
NextContinuationToken: result.nextContinuationToken,
|
||||
}),
|
||||
...(result.commonPrefixes.length > 0 && {
|
||||
CommonPrefixes: result.commonPrefixes.map((prefix) => ({
|
||||
Prefix: prefix,
|
||||
})),
|
||||
}),
|
||||
Contents: result.contents.map((obj) => ({
|
||||
Key: obj.key,
|
||||
LastModified: obj.lastModified.toISOString(),
|
||||
ETag: `"${obj.md5}"`,
|
||||
Size: obj.size,
|
||||
StorageClass: 'STANDARD',
|
||||
})),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// List Objects V1 response
|
||||
await ctx.sendXML({
|
||||
ListBucketResult: {
|
||||
'@_xmlns': 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
Name: bucket,
|
||||
Prefix: result.prefix || '',
|
||||
MaxKeys: result.maxKeys,
|
||||
IsTruncated: result.isTruncated,
|
||||
...(result.delimiter && { Delimiter: result.delimiter }),
|
||||
...(result.commonPrefixes.length > 0 && {
|
||||
CommonPrefixes: result.commonPrefixes.map((prefix) => ({
|
||||
Prefix: prefix,
|
||||
})),
|
||||
}),
|
||||
Contents: result.contents.map((obj) => ({
|
||||
Key: obj.key,
|
||||
LastModified: obj.lastModified.toISOString(),
|
||||
ETag: `"${obj.md5}"`,
|
||||
Size: obj.size,
|
||||
StorageClass: 'STANDARD',
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user