API 网关路径始终指向我的 Express 服务器中的基本路径

问题描述 投票:0回答:1

我有一个 Api Gateway Rest Api,它受 Cognito Authorizer 保护,并通过 VPCLink 集成来访问运行 NodeJs Express 应用程序的容器。

完整流程如下

Api 网关 -> VPCLink -> NLB(网络负载均衡器) -> ALB(应用程序负载均衡器) -> 容器

这是我的 CDK 设置

Fargate 设置


import * as path from 'path';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from "aws-cdk-lib/aws-ecr";
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
import { DockerImageAsset } from 'aws-cdk-lib/aws-ecr-assets';
import { DockerImageName, ECRDeployment } from 'cdk-ecr-deployment';
import { StackProps } from 'aws-cdk-lib';
import { NetworkLoadBalancer, NetworkTargetGroup, TargetType } from 'aws-cdk-lib/aws-elasticloadbalancingv2';


export const setupFargate = ($this: any, cognitoBetaUserpool: any, cognitoGammaUserpool: any, cognitoProdUserpool: any, props?: StackProps) => {

    const betaFargate = setupService($this, 'beta', cognitoBetaUserpool, props);
    const gammaFargate = setupService($this, 'gamma', cognitoGammaUserpool, props);
    const prodFargate = setupService($this, 'prod', cognitoProdUserpool, props);

    return {betaFargate, gammaFargate, prodFargate};   
};

const setupService = ($this: any, stage: string, userPool: any, props?: StackProps) => {
    const vpc = new ec2.Vpc($this, `BackendVpc-${stage}`, { 
        ip_addresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
        enableDnsSupport: true,
        subnetConfiguration: [
          {
              cidrMask: 24,
              name: 'private',
              subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
          },
          {
            cidrMask: 24,
            name: 'public',
            subnetType: ec2.SubnetType.PUBLIC,
        }
      ],
      } as ec2.VpcProps);

    configVPC(vpc);

    const cluster = new ecs.Cluster($this, `BackendCluster-${stage}`, { vpc: vpc, enableFargateCapacityProviders: true });

    const imageRepo = new ecr.Repository($this, `BackendRepo-${stage}`, {
        repositoryName: `backend-repo-${stage}`
    });

    const dockerImage = new DockerImageAsset($this,`DockerImage-${stage}`,{
        directory: path.join(__dirname, '..', '..', 'backend'),
    });

    const deployment = new ECRDeployment($this, `DeployDockerImage-${stage}`, {
        src: new DockerImageName(dockerImage.imageUri),
        dest: new DockerImageName(`${imageRepo.repositoryUri}:latest`),
    });

    dockerImage.node.addDependency(imageRepo);
    deployment.node.addDependency(imageRepo);

    const service = new ApplicationLoadBalancedFargateService($this, `BackendService-${stage}`, {
        serviceName: `BackendService-${stage}`,
        cluster: cluster,
        cpu: 256, 
        memoryLimitMiB: 2048,
        taskImageOptions: {
            image: ecs.ContainerImage.fromEcrRepository(imageRepo, 'latest'),
            containerName: `backend-repo-${stage}`,
            containerPort: 80,
            environment: { 
                PORT: "80",
                STAGE: stage,
                AWS_DEFAULT_REGION: props?.env?.region || "",
                USERPOOLID: userPool.userPoolId,

            }
        },
        desiredCount: 1,
        publicLoadBalancer: false,
    });

    const nlb = new NetworkLoadBalancer($this, `BackendNLB-${stage}`, {
        vpc: vpc,
        crossZoneEnabled: true,
        internetFacing: false,
        vpcSubnets: {
            subnets: vpc.privateSubnets
        } 
    });

    const nlbListener = nlb.addListener(`BackendNLBListener-${stage}`, {
        port: 80,
    });

    const nlbTargetGroup = new NetworkTargetGroup($this, `BackendNLBTargetGroup-${stage}`, {
        port: 80,
        vpc: vpc,
        targetType: TargetType.ALB,
         // add target manually in aws console since cant find way to do it with cdk 
    });


    nlbListener.addTargetGroups(`BackendNLBAddingTargetGroup-${stage}`, nlbTargetGroup);

    return {service, imageRepo, nlb}; 
};


const configVPC = (vpc: any) => {
    // Configure VPC for required services
    // ECR images are stored in s3, and thus s3 is needed
    vpc.addGatewayEndpoint('S3Endpoint', {
        service: ec2.GatewayVpcEndpointAwsService.S3,
    });

    vpc.addGatewayEndpoint('DynamoDbEndpoint', {
        service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
    });

    vpc.addInterfaceEndpoint('EcrEndpoint', {
        service: ec2.InterfaceVpcEndpointAwsService.ECR,
        privateDnsEnabled: true,
        open: true,
    });

    vpc.addInterfaceEndpoint('EcrDockerEndpoint', {
        service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
        privateDnsEnabled: true,
        open: true,
    });

    vpc.addInterfaceEndpoint('LogsEndpoint', {
        service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
        privateDnsEnabled: true,
        open: true,
    });

    vpc.addInterfaceEndpoint('ApiGatewayEndpoint', {
        service: ec2.InterfaceVpcEndpointAwsService.APIGATEWAY,
        privateDnsEnabled: true,
        open: true,
    });

    vpc.addInterfaceEndpoint('EcsEndpoint', {
        service: ec2.InterfaceVpcEndpointAwsService.ECS,
        privateDnsEnabled: true,
        open: true,
    });
};


API网关设置


import { RestApi, CorsOptions, CognitoUserPoolsAuthorizer, HttpIntegration, AuthorizationType, VpcLink, PassthroughBehavior } from 'aws-cdk-lib/aws-apigateway';

export const setupApiGateway = ($this: any, betaUserPool: any, gammaUserPool: any, prodUserPool: any, betaFargate: any, gammaFargate: any, prodFargate: any) => {
    // Create the API Gateway for each stage
    const betaGateway = setupIndividualApiGateway($this, 'beta', betaUserPool, betaFargate);
    const gammaGateway = setupIndividualApiGateway($this, 'gamma', gammaUserPool, gammaFargate);
    const prodGateway = setupIndividualApiGateway($this, 'prod', prodUserPool, prodFargate);
}; 

const setupIndividualApiGateway = ($this: any, stage: any, userPool: any, fargate: any) => {
    const corsOptions: CorsOptions = {
        allowOrigins: ['*'], // Update with the appropriate origins
        allowMethods: ['*'], // Add other allowed methods as needed
        allowHeaders: ['*'], // Add other allowed headers as needed
        exposeHeaders: ['*'],
        allowCredentials: true, // Enable credentials (cookies) in CORS requests
      };

    const gateway = new RestApi($this, `BackendGateway${stage}`, {
        restApiName: `Backend${stage}`,
        defaultCorsPreflightOptions: corsOptions,
    });

    const authorizer = new CognitoUserPoolsAuthorizer($this, `${stage}CognitoAuthorizer`, {
        cognitoUserPools: [userPool]
    });

    const vpcLink = new VpcLink($this, `BackendVpcLink-${stage}`, {
        vpcLinkName: `BackencVpcLink-${stage}`,
        targets: [fargate.nlb]
    });

    const integration = new HttpIntegration(`http://${fargate.nlb.loadBalancerDnsName}`, {
        proxy: true,
        options: {
            vpcLink: vpcLink,
            passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
        }
    });

    gateway.root.addMethod('GET', integration, {
        authorizer: authorizer,
        authorizationType: AuthorizationType.COGNITO,
    });

    gateway.root.addMethod('POST', integration, {
        authorizer: authorizer,
        authorizationType: AuthorizationType.COGNITO,
    });

    return gateway;
};

她是我的 Node js Express 服务器

const express = require("express");
const ip = require('ip');
const ipAddress = ip.address();
const bodyParser = require('body-parser');
const cors = require('cors');
const port = process.env.PORT || 80;
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Enable CORS for all methods
app.use(cors());
app.options('*', cors());

// Enable CORS for all methods
app.use((req, res, next) => {
    console.log(req.originalUrl);
    res.header("Access-Control-Allow-Headers", "*");
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "*");
    res.header("Access-Control-Expose-Headers", "*");
    res.header("Access-Control-Allow-Credentials", "true");
    next();
});

app.get("/api", (req,res ) => {
    console.log(req.originalUrl);
    res.status(200).send({data: `OK this is the /api path`});
});

app.get("/prod/api", (req,res ) => {
    console.log(req.originalUrl);
    res.status(200).send({data: `OK this is the /prod/api path`});
});

app.get("/health", (req,res ) => {
    console.log(req.originalUrl);
    res.status(200).send({data: `OK this is the /health`});
});

app.get("/", (req,res ) => {
    console.log(req.originalUrl);
    res.status(200).send({data: `OK this is the / path`});
});

app.use((req, res, next) => {
    try {
        const result = `Please try GET on /posts, /post?id=xyz, or a POST to /post with JSON {\"id\":\"123\",\"title\":\"Fargate test\"}`;
        res.contentType("application/json").send(result);
    } catch (err) {
        next(err);
    }
});
// Error middleware must be defined last
app.use((err, req, res, next) => {
    console.error(err.message);
    if (!err.statusCode) err.statusCode = 500; // If err has no specified error code, set error code to 'Internal Server Error (500)'
    res
        .status(err.statusCode)
        .json({ message: err.message })
        .end();
});
app.listen(port, () => {
    console.log('app listening at ip ' + ipAddress + ' and port ' + port);
});

问题是,无论我尝试从API网关访问哪条路径

/
/API
/健康

它们都被路由到基本路径/

我认为这与 NLB 到 ALB 有关,但我还没有找到任何其他方法可以将 Api 网关与私有 ALB 集成。

amazon-web-services express aws-api-gateway aws-application-load-balancer nlb
1个回答
0
投票

想通了!这与我的 Api 网关的集成有关。我必须创建一个代理(又名 {proxy+} 路由),然后在负载均衡器端点中,您必须将参数作为 url {proxy} 的一部分包含进来,然后您必须进行一些请求参数映射,以便 {proxy} 实际上被替换为请求路径中有什么。希望这有帮助。

这是我为使其工作而更改的代码。

const integration = new HttpIntegration(`http://${fargate.nlb.loadBalancerDnsName}/{proxy}`, {
    proxy: true,
    options: {
        vpcLink: vpcLink,
        passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
        requestParameters: {
            'integration.request.path.proxy': 'method.request.path.proxy'
        }
    }
});

gateway.root.addProxy({
    defaultIntegration: integration,
    defaultMethodOptions: {
        operationName: 'ANY',
        authorizationType: AuthorizationType.COGNITO,
        authorizer: authorizer,
        requestParameters: {'method.request.path.proxy': true}
    },
    defaultCorsPreflightOptions: corsOptions
});
© www.soinside.com 2019 - 2024. All rights reserved.