我正在使用Nest框架构建GraphQL API,并试图将3rd Party Express中间件(express-rate-limit和express-slow-down)实现为一些查询和变异。
问题在于所有graphql突变和查询都使用相同的端点,因此我无法明确告诉中间件将应用于哪个查询或突变,因为您只能使用路由的路径来做到这一点(跨路径是相同的) API)。
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common'
import * as rateLimit from 'express-rate-limit'
import * as RedisStore from 'rate-limit-redis'
import { RedisClient } from 'redis'
@Module({
providers: [],
exports: [],
})
export default class SecurityModule implements NestModule
{
constructor(protected readonly redisClient: RedisClient)
{
}
configure(consumer: MiddlewareConsumer)
{
consumer.apply(
new rateLimit({
max: 300,
windowMs: 15 * 60 * 1000,
store: new RedisStore({ client: this.redisClient }),
})).forRoutes({ path: '/graphql', method: RequestMethod.ALL }) // this would apply the middleware to all queries and mutations
}
}
因此,我为此尝试同时使用了防护和拦截器,但失败了。
失败的原因很明显。
Error: Can't set headers after they are sent
被抛出。
/* !!! My Interceptor would like quite identical */
import { ExecutionContext, Injectable, CanActivate } from '@nestjs/common'
import * as speedLimit from 'express-slow-down'
import { Request, Response } from 'express'
@Injectable()
export default class SpeedLimitGuard implements CanActivate
{
constructor(
protected readonly options: speedLimit.Options,
) {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const { req, res }: { req: Request, res: Response } = context.getArgs()[2]
speedLimit({ ...this.options })(req, res, req.next)
return true
}
}
import { NestInterceptor, ExecutionContext, Injectable, INestApplication, INestExpressApplication } from '@nestjs/common'
import { Observable } from 'rxjs'
import * as speedLimit from 'express-slow-down'
// import { Request, Response } from 'express'
import { ApplicationReferenceHost } from '@nestjs/core'
import { RedisClient } from 'redis'
import * as RedisStore from 'rate-limit-redis'
@Injectable()
export default class SpeedLimitInterceptor implements NestInterceptor
{
constructor(private readonly appRefHost: ApplicationReferenceHost,
private readonly redisClient: RedisClient, )
{}
intercept<T>(context: ExecutionContext, call$: Observable<T>): Observable<T>
{
// const { req: request, res: response }: { req: Request, res: Response } = context.getArgs()[2]
const httpServer = this.appRefHost.applicationRef
const app: INestApplication & INestExpressApplication = httpServer.getInstance()
app.use(speedLimit({
delayAfter: 1,
store: new RedisStore({
prefix: 'test_',
client: this.redisClient,
}),
}))
app.use((req, res, next) => {
console.log('is middleware triggered', { req, res })
next()
})
return call$
}
}
是否可以将第3方快速表达中间件显式应用于GraphQL突变/查询?
答案 0 :(得分:2)
所以从底层看,警卫正在工作,因为我是能证明这一点的活人豆:
@Query('getHome')
@UseGuards(GraphqlGuard)
async findOneById(@Args('id') id: string): Promise<HomeEntity> {
return await this.homeService.findOneById(id);
}
它正在工作。
这是GraphqlGuard.ts
import {ExecutionContext, Injectable} from '@nestjs/common';
import {GqlExecutionContext} from '@nestjs/graphql';
import {AuthGuard} from '@nestjs/passport';
import {ExecutionContextHost} from '@nestjs/core/helpers/execution-context.host';
import {Observable} from 'rxjs';
@Injectable()
export class GraphqlGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const ctx = GqlExecutionContext.create(context);
const {req} = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
}
但是要使用上下文,您必须使其适合您,因此,无论您在哪里传递graphql配置,都存在一个上下文callback
,对我来说,它看起来像这样:
context: (context) => {
let req = context.req;
if (context.connection) {
req = context.connection.context.req;
}
return {req};
}
我正在这里检查来自websocket的上下文连接。我正在使用全局拦截器,因此它们的运行就像是一种魅力。但是您仍然可以使用@UseInterceptors(SomeInterceptor)
装饰器,它也可以工作。顺便说一句,中间件,最后,我不需要任何防护,管道,验证器和拦截器就足够了。
致谢。