refactor progress
This commit is contained in:
25
packages/s3/src/app/S3AppConfigTypes.tsx
Normal file
25
packages/s3/src/app/S3AppConfigTypes.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface S3RateLimitConfig {
|
||||
enabled: boolean;
|
||||
maxAttempts: number;
|
||||
windowMs: number;
|
||||
skipPaths?: Array<string>;
|
||||
}
|
||||
71
packages/s3/src/app/S3ErrorHandling.tsx
Normal file
71
packages/s3/src/app/S3ErrorHandling.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createErrorHandler} from '@fluxer/errors/src/ErrorHandler';
|
||||
import type {LoggerInterface} from '@fluxer/logger/src/LoggerInterface';
|
||||
import {S3Error} from '@fluxer/s3/src/errors/S3Error';
|
||||
import {captureException} from '@fluxer/sentry/src/Sentry';
|
||||
import type {Hono} from 'hono';
|
||||
import type {HonoEnv} from '../types/HonoEnv';
|
||||
|
||||
interface SetupS3ErrorHandlingOptions {
|
||||
app: Hono<HonoEnv>;
|
||||
logger: LoggerInterface;
|
||||
}
|
||||
|
||||
export function setupS3ErrorHandling(options: SetupS3ErrorHandlingOptions): void {
|
||||
const {app, logger} = options;
|
||||
|
||||
const errorHandler = createErrorHandler({
|
||||
includeStack: false,
|
||||
responseFormat: 'xml',
|
||||
logError: (err, ctx) => {
|
||||
const isExpectedError = err instanceof Error && 'isExpected' in err && err.isExpected;
|
||||
|
||||
if (!(err instanceof S3Error || isExpectedError)) {
|
||||
captureException(err);
|
||||
}
|
||||
|
||||
logger.error(
|
||||
{
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
requestId: ctx.get('requestId'),
|
||||
},
|
||||
'Request error',
|
||||
);
|
||||
},
|
||||
customHandler: (err, ctx) => {
|
||||
if (err instanceof S3Error) {
|
||||
err.requestId = ctx.get('requestId');
|
||||
return err.getResponse();
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
app.onError(errorHandler);
|
||||
|
||||
app.notFound((ctx) => {
|
||||
const s3Error = new S3Error('NoSuchKey', 'The specified resource was not found.', {
|
||||
requestId: ctx.get('requestId'),
|
||||
});
|
||||
return s3Error.getResponse();
|
||||
});
|
||||
}
|
||||
89
packages/s3/src/app/S3MiddlewareSetup.tsx
Normal file
89
packages/s3/src/app/S3MiddlewareSetup.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Headers} from '@fluxer/constants/src/Headers';
|
||||
import {applyMiddlewareStack} from '@fluxer/hono/src/middleware/MiddlewareStack';
|
||||
import type {MetricsCollector} from '@fluxer/hono_types/src/MetricsTypes';
|
||||
import type {TracingOptions} from '@fluxer/hono_types/src/TracingTypes';
|
||||
import type {LoggerInterface} from '@fluxer/logger/src/LoggerInterface';
|
||||
import type {RateLimitService} from '@fluxer/rate_limit/src/RateLimitService';
|
||||
import type {S3RateLimitConfig} from '@fluxer/s3/src/app/S3AppConfigTypes';
|
||||
import type {S3AuthConfig} from '@fluxer/s3/src/middleware/S3AuthMiddleware';
|
||||
import {createS3AuthMiddleware} from '@fluxer/s3/src/middleware/S3AuthMiddleware';
|
||||
import type {IS3Service} from '@fluxer/s3/src/s3/S3Service';
|
||||
import type {HonoEnv} from '@fluxer/s3/src/types/HonoEnv';
|
||||
import type {Hono} from 'hono';
|
||||
import {createMiddleware} from 'hono/factory';
|
||||
|
||||
interface SetupS3MiddlewareOptions {
|
||||
app: Hono<HonoEnv>;
|
||||
logger: LoggerInterface;
|
||||
s3Service: IS3Service;
|
||||
authConfig: S3AuthConfig;
|
||||
metricsCollector?: MetricsCollector;
|
||||
tracing?: TracingOptions;
|
||||
rateLimitService?: RateLimitService | null;
|
||||
rateLimitConfig?: S3RateLimitConfig | null;
|
||||
}
|
||||
|
||||
export function setupS3Middleware(options: SetupS3MiddlewareOptions): void {
|
||||
const {app, logger, s3Service, authConfig, metricsCollector, tracing, rateLimitService, rateLimitConfig} = options;
|
||||
|
||||
const serviceMiddleware = createMiddleware<HonoEnv>(async (ctx, next) => {
|
||||
ctx.set('s3Service', s3Service);
|
||||
await next();
|
||||
});
|
||||
|
||||
applyMiddlewareStack(app, {
|
||||
requestId: {headerName: Headers.X_AMZ_REQUEST_ID},
|
||||
tracing,
|
||||
metrics: metricsCollector
|
||||
? {
|
||||
enabled: true,
|
||||
collector: metricsCollector,
|
||||
skipPaths: ['/_health'],
|
||||
}
|
||||
: undefined,
|
||||
logger: {
|
||||
log: (data) => {
|
||||
logger.info(
|
||||
{
|
||||
method: data.method,
|
||||
path: data.path,
|
||||
status: data.status,
|
||||
durationMs: data.durationMs,
|
||||
},
|
||||
'Request completed',
|
||||
);
|
||||
},
|
||||
},
|
||||
rateLimit:
|
||||
rateLimitService && rateLimitConfig?.enabled
|
||||
? {
|
||||
enabled: true,
|
||||
service: rateLimitService,
|
||||
maxAttempts: rateLimitConfig.maxAttempts,
|
||||
windowMs: rateLimitConfig.windowMs,
|
||||
skipPaths: rateLimitConfig.skipPaths ?? ['/_health'],
|
||||
}
|
||||
: undefined,
|
||||
customMiddleware: [serviceMiddleware, createS3AuthMiddleware(authConfig, logger)],
|
||||
skipErrorHandler: true,
|
||||
});
|
||||
}
|
||||
60
packages/s3/src/app/S3RateLimitResolver.tsx
Normal file
60
packages/s3/src/app/S3RateLimitResolver.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {ICacheService} from '@fluxer/cache/src/ICacheService';
|
||||
import {KVCacheProvider} from '@fluxer/cache/src/providers/KVCacheProvider';
|
||||
import type {IKVProvider} from '@fluxer/kv_client/src/IKVProvider';
|
||||
import {KVClient} from '@fluxer/kv_client/src/KVClient';
|
||||
import {throwKVRequiredError} from '@fluxer/rate_limit/src/KVRequiredError';
|
||||
import {RateLimitService} from '@fluxer/rate_limit/src/RateLimitService';
|
||||
import type {S3RateLimitConfig} from '@fluxer/s3/src/app/S3AppConfigTypes';
|
||||
|
||||
interface ResolveS3RateLimitServiceOptions {
|
||||
kvUrl?: string;
|
||||
rateLimitService?: RateLimitService | null;
|
||||
rateLimitConfig?: S3RateLimitConfig | null;
|
||||
}
|
||||
|
||||
export function resolveS3RateLimitService(options: ResolveS3RateLimitServiceOptions): RateLimitService | null {
|
||||
const {kvUrl, rateLimitService, rateLimitConfig} = options;
|
||||
|
||||
if (rateLimitService) {
|
||||
return rateLimitService;
|
||||
}
|
||||
|
||||
if (!rateLimitConfig?.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!kvUrl) {
|
||||
throwKVRequiredError({
|
||||
serviceName: 'S3 service',
|
||||
configPath: 'kvUrl option',
|
||||
fluxerServerHint: 'rateLimitService is passed in as an option',
|
||||
});
|
||||
}
|
||||
|
||||
const kvProvider = createKVProvider(kvUrl);
|
||||
const cacheService: ICacheService = new KVCacheProvider({client: kvProvider});
|
||||
return new RateLimitService(cacheService);
|
||||
}
|
||||
|
||||
function createKVProvider(kvUrl: string): IKVProvider {
|
||||
return new KVClient({url: kvUrl});
|
||||
}
|
||||
50
packages/s3/src/app/S3ResponseHeadersMiddleware.tsx
Normal file
50
packages/s3/src/app/S3ResponseHeadersMiddleware.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Headers} from '@fluxer/constants/src/Headers';
|
||||
import type {HonoEnv} from '@fluxer/s3/src/types/HonoEnv';
|
||||
import type {Hono} from 'hono';
|
||||
|
||||
export function setupS3ResponseHeadersMiddleware(app: Hono<HonoEnv>): void {
|
||||
app.use('*', async (ctx, next) => {
|
||||
await next();
|
||||
|
||||
ctx.header(Headers.X_AMZ_ID_2, ctx.get('requestId'));
|
||||
ctx.header('Server', 'FluxerS3');
|
||||
|
||||
const origin = ctx.req.header('origin');
|
||||
if (origin) {
|
||||
ctx.header('Access-Control-Allow-Origin', origin);
|
||||
ctx.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, HEAD, OPTIONS');
|
||||
ctx.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Authorization, Content-Type, X-Amz-Date, X-Amz-Content-Sha256, X-Amz-User-Agent, X-Amz-Security-Token, X-Amz-Meta-*',
|
||||
);
|
||||
ctx.header(
|
||||
'Access-Control-Expose-Headers',
|
||||
'ETag, x-amz-request-id, x-amz-id-2, x-amz-version-id, x-amz-delete-marker',
|
||||
);
|
||||
ctx.header('Access-Control-Max-Age', '3600');
|
||||
}
|
||||
});
|
||||
|
||||
app.options('*', (ctx) => {
|
||||
return ctx.body(null, 200);
|
||||
});
|
||||
}
|
||||
32
packages/s3/src/app/S3RouteRegistrar.tsx
Normal file
32
packages/s3/src/app/S3RouteRegistrar.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {BucketController} from '@fluxer/s3/src/s3/BucketController';
|
||||
import {ObjectController} from '@fluxer/s3/src/s3/ObjectController';
|
||||
import type {HonoEnv} from '@fluxer/s3/src/types/HonoEnv';
|
||||
import type {Hono} from 'hono';
|
||||
|
||||
export function registerS3Routes(app: Hono<HonoEnv>): void {
|
||||
app.get('/_health', (ctx) => {
|
||||
return ctx.json({ok: true}, 200);
|
||||
});
|
||||
|
||||
ObjectController(app);
|
||||
BucketController(app);
|
||||
}
|
||||
Reference in New Issue
Block a user