Применение Middleware-подобного механизма к запросам и мутациям резолверов - PullRequest
0 голосов
/ 05 февраля 2019

Я строю API-интерфейс GraphQL с использованием среды Nest и пытаюсь внедрить стороннее экспресс-промежуточное ПО ( express-rate-limit и express-slow-down ) внекоторые запросы и мутации.

Проблема в том, что все мутации graphql и запросы используют одну и ту же конечную точку, поэтому я не могу точно сказать, к какому запросу или мутациям будет применяться промежуточное программное обеспечение, потому что вы можете сделать это только с помощью routeпуть (который одинаков в 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$
    }
}

Есть ли способ явно применить промежуточное промежуточное программное обеспечение сторонних производителей для мутации / запроса GraphQL?

1 Ответ

0 голосов
/ 05 февраля 2019

Итак, снизу, охранники работают, потому что я живой человек, который может это доказать:

    @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) декоратор, и это тоже работает.И между прочим Middlewares, в конце концов, мне не нужны никакие из них охранники, каналы, валидаторы и перехватчики, для меня вполне достаточно.

С уважением.

...