Skip to content

Commit

Permalink
account-api: added timeout and body size interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
aramikm committed Sep 17, 2024
1 parent 6f35e95 commit 7fc62bb
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 3 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ services/*/docs/index.html
openapi-specs/*
**/target/**
jest.config.json
docs/*
10 changes: 8 additions & 2 deletions apps/account-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { ConfigService } from '#account-lib/config/config.service';
import { initSwagger } from '#account-lib/config/swagger_config';
import { ApiModule } from './api.module';
import { TimeoutInterceptor } from '#account-lib/utils/timeout.interceptor';
import { NestExpressApplication } from '@nestjs/platform-express';

const logger = new Logger('main');

Expand All @@ -30,8 +32,9 @@ async function bootstrap() {
process.exit(1);
});

const app = await NestFactory.create(ApiModule, {
const app = await NestFactory.create<NestExpressApplication>(ApiModule, {
logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error', 'warn', 'log'],
rawBody: true,
});

// Get event emitter & register a shutdown listener
Expand All @@ -43,10 +46,13 @@ async function bootstrap() {
});

try {
const configService = app.get<ConfigService>(ConfigService);

app.enableShutdownHooks();
app.useGlobalPipes(new ValidationPipe());
app.useGlobalInterceptors(new TimeoutInterceptor(configService.apiTimeoutMs));
app.useBodyParser('json', { limit: configService.apiBodyJsonLimit });

const configService = app.get<ConfigService>(ConfigService);
await initSwagger(app, '/docs/swagger');
logger.log(`Listening on port ${configService.apiPort}`);
await app.listen(configService.apiPort);
Expand Down
2 changes: 2 additions & 0 deletions developer-docs/account/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ This application recognizes the following environment variables:
| `WEBHOOK_FAILURE_THRESHOLD` | Number of failures allowed in the provider webhook before the service is marked down | > 0 | | 3 |
| `WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 |
| `GRAPH_ENVIRONMENT_TYPE` | Graph environment type. | Mainnet\|TestnetPaseo | Y | |
| `API_TIMEOUT_MS` | Api timeout limit in milliseconds | > 0 | | 5000 |
| `API_BODY_JSON_LIMIT` | Api json body size limit in string (some examples: 100kb or 5mb or etc) | string | | 1mb |
8 changes: 7 additions & 1 deletion env-files/account.template.env
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,10 @@ CACHE_KEY_PREFIX=account:

# Graph Sdk environment
GRAPH_ENVIRONMENT_TYPE=TestnetPaseo
#GRAPH_ENVIRONMENT_TYPE=Mainnet
#GRAPH_ENVIRONMENT_TYPE=Mainnet

# Api timeout limit in milliseconds
API_TIMEOUT_MS=5000

# Api json body size limit in string (some examples: 100kb or 5mb or etc)
API_BODY_JSON_LIMIT=1mb
15 changes: 15 additions & 0 deletions libs/account-lib/src/config/config.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ describe('AccountServiceConfig', () => {
HEALTH_CHECK_MAX_RETRIES: undefined,
CAPACITY_LIMIT: undefined,
CACHE_KEY_PREFIX: undefined,
API_TIMEOUT_MS: undefined,
API_BODY_JSON_LIMIT: undefined,
};

beforeAll(() => {
Expand Down Expand Up @@ -121,6 +123,11 @@ describe('AccountServiceConfig', () => {
await expect(setupConfigService({ CAPACITY_LIMIT: undefined, ...env })).rejects.toBeDefined();
});

it('invalid api timeout limit should fail', async () => {
const { API_TIMEOUT_MS: dummy, ...env } = ALL_ENV;
await expect(setupConfigService({ API_TIMEOUT_MS: 0, ...env })).rejects.toBeDefined();
});

it('invalid capacity limit should fail', async () => {
const { CAPACITY_LIMIT: dummy, ...env } = ALL_ENV;
await expect(
Expand Down Expand Up @@ -247,5 +254,13 @@ describe('AccountServiceConfig', () => {
it('should get cache key prefix', () => {
expect(accountServiceConfig.cacheKeyPrefix).toStrictEqual(ALL_ENV.CACHE_KEY_PREFIX?.toString());
});

it('should get api timeout limit milliseconds', () => {
expect(accountServiceConfig.apiTimeoutMs).toStrictEqual(parseInt(ALL_ENV.API_TIMEOUT_MS as string, 10));
});

it('should get api json body size limit', () => {
expect(accountServiceConfig.apiBodyJsonLimit).toStrictEqual(ALL_ENV.API_BODY_JSON_LIMIT?.toString());
});
});
});
10 changes: 10 additions & 0 deletions libs/account-lib/src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export interface ConfigEnvironmentVariables {
HEALTH_CHECK_MAX_RETRIES: number;
CAPACITY_LIMIT: string;
CACHE_KEY_PREFIX: string;
API_TIMEOUT_MS: number;
API_BODY_JSON_LIMIT: string;
}

/// Config service to get global app and provider-specific config values.
Expand Down Expand Up @@ -174,4 +176,12 @@ export class ConfigService implements OnModuleInit {
public get graphEnvironmentType(): keyof EnvironmentType {
return this.nestConfigService.get<keyof EnvironmentType>('GRAPH_ENVIRONMENT_TYPE')!;
}

public get apiTimeoutMs(): number {
return this.nestConfigService.get<number>('API_TIMEOUT_MS') ?? 5000;
}

public get apiBodyJsonLimit(): string {
return this.nestConfigService.get('API_BODY_JSON_LIMIT') ?? '1mb';
}
}
2 changes: 2 additions & 0 deletions libs/account-lib/src/config/env.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,7 @@ export const configModuleOptions = (allowReadOnly: boolean): ConfigModuleOptions
return value;
})
.required(),
API_TIMEOUT_MS: Joi.number().min(1).default(5000),
API_BODY_JSON_LIMIT: Joi.string().default('1mb'),
}),
});
24 changes: 24 additions & 0 deletions libs/account-lib/src/utils/timeout.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
timeoutMs: number;

constructor(timeoutMs: number) {
this.timeoutMs = timeoutMs;
}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(this.timeoutMs),
catchError((err) => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}

0 comments on commit 7fc62bb

Please sign in to comment.