Skip to content

Commit

Permalink
Set Cache-Control to no-cache (#1356)
Browse files Browse the repository at this point in the history
- Adds a Nest interceptor which, when applied, sets the `Cache-Control` directive to `no-cache`. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives
- Adjusts the redirects on the `RelayLegacyController`:
  * With `res.redirect`, a response was being sent to the clients, and the interceptor would fail to set the header (resulting in a 500 – "Cannot set headers after they are sent to the client").
  * By returning an object with `{ url }` with the intended redirect in addition to the decorator `@Redirect`, the header is set correctly before the body is sent out to the clients. See https://docs.nestjs.com/controllers#redirection
  • Loading branch information
fmrsabino authored Apr 4, 2024
1 parent 061dd25 commit 02647a9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 16 deletions.
5 changes: 5 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { RelayControllerModule } from '@/routes/relay/relay.controller.module';
import { SubscriptionControllerModule } from '@/routes/subscriptions/subscription.module';
import { LockingModule } from '@/routes/locking/locking.module';
import { ZodErrorFilter } from '@/routes/common/filters/zod-error.filter';
import { CacheControlInterceptor } from '@/routes/common/interceptors/cache-control.interceptor';

@Module({})
export class AppModule implements NestModule {
Expand Down Expand Up @@ -116,6 +117,10 @@ export class AppModule implements NestModule {
provide: APP_INTERCEPTOR,
useClass: RouteLoggerInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: CacheControlInterceptor,
},
{
provide: APP_FILTER,
useClass: GlobalErrorFilter,
Expand Down
37 changes: 37 additions & 0 deletions src/routes/common/interceptors/cache-control.interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
Controller,
Get,
INestApplication,
UseInterceptors,
} from '@nestjs/common';
import { CacheControlInterceptor } from '@/routes/common/interceptors/cache-control.interceptor';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';

@Controller()
@UseInterceptors(CacheControlInterceptor)
class TestController {
@Get()
test(): void {
return;
}
}

describe('CacheControlInterceptor tests', () => {
let app: INestApplication;

beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [TestController],
}).compile();

app = module.createNestApplication();
await app.init();
});

it('should set the Cache-Control header to no-cache', () => {
return request(app.getHttpServer())
.get('/')
.expect('Cache-Control', 'no-cache');
});
});
25 changes: 25 additions & 0 deletions src/routes/common/interceptors/cache-control.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';

/**
* This interceptor can be used to set the `Cache-Control` header to `no-cache`.
*/
@Injectable()
export class CacheControlInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<unknown> | Promise<Observable<unknown>> {
return next.handle().pipe(
tap(() => {
const response = context.switchToHttp().getResponse();
response.header('Cache-Control', 'no-cache');
}),
);
}
}
25 changes: 9 additions & 16 deletions src/routes/relay/relay.legacy.controller.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,35 @@
import { RelayLegacyDto } from '@/routes/relay/entities/relay.legacy.dto.entity';
import { RelayLegacyDtoValidationPipe } from '@/routes/relay/pipes/relay.legacy.validation.pipe';
import {
Body,
Controller,
Post,
Get,
HttpStatus,
Res,
Param,
Body,
Post,
Redirect,
} from '@nestjs/common';
import { Response } from 'express';

@Controller({
version: '1',
path: 'relay',
})
export class RelayLegacyController {
@Post()
@Redirect(undefined, HttpStatus.PERMANENT_REDIRECT)
relay(
@Body(RelayLegacyDtoValidationPipe)
relayLegacyDto: RelayLegacyDto,
@Res() res: Response,
): void {
res.redirect(
HttpStatus.PERMANENT_REDIRECT,
`/v1/chains/${relayLegacyDto.chainId}/relay`,
);
): { url: string } {
return { url: `/v1/chains/${relayLegacyDto.chainId}/relay` };
}

@Get('/:chainId/:safeAddress')
@Redirect(undefined, HttpStatus.MOVED_PERMANENTLY)
getRelaysRemaining(
@Param('chainId') chainId: string,
@Param('safeAddress') safeAddress: string,
@Res() res: Response,
): void {
res.redirect(
HttpStatus.MOVED_PERMANENTLY,
`/v1/chains/${chainId}/relay/${safeAddress}`,
);
): { url: string } {
return { url: `/v1/chains/${chainId}/relay/${safeAddress}` };
}
}

0 comments on commit 02647a9

Please sign in to comment.