【问题标题】:AWS CDK passing API Gateway URL to static site in same StackAWS CDK 将 API Gateway URL 传递到同一堆栈中的静态站点
【发布时间】:2020-05-21 07:03:12
【问题描述】:

我正在尝试在单个堆栈中部署 S3 静态网站和 API 网关/lambda。

S3 静态站点中的 javascript 调用 lambda 以填充 HTML 列表,但它需要知道用于 lambda 集成的 API Gateway URL。

目前,我像这样生成一个 RestApi...

    const handler = new lambda.Function(this, "TestHandler", {
      runtime: lambda.Runtime.NODEJS_10_X,
      code: lambda.Code.asset("build/test-service"),
      handler: "index.handler",
      environment: {
      }
    });

    this.api = new apigateway.RestApi(this, "test-api", {
      restApiName: "Test Service"
    });    

    const getIntegration = new apigateway.LambdaIntegration(handler, {
      requestTemplates: { "application/json": '{ "statusCode": "200" }' }
    });

    const apiUrl = this.api.url;

但是在 cdk deploy 上,apiUrl =

"https://${Token[TOKEN.39]}.execute-api.${Token[AWS::Region.4]}.${Token[AWS::URLSuffix.1]}/${Token[TOKEN.45]}/"

因此,在静态站点需要该值之前,不会解析/生成 url。

如何计算/查找/获取 API Gateway URL 并更新 cdk deploy 上的 javascript?

或者有更好的方法吗?即静态javascript是否有一种优雅的方式来检索lambda api网关url?

谢谢。

【问题讨论】:

  • 只是为了确保,您想创建一个新的 api 网关,然后使用 url ?
  • 是的,我正在研究创建一个名为 config.json 的 S3Object,其内容为 { "apiurl" : !Sub "https://${restApiId}.execute-api.${AWS: :Region}.amazonaws.com/${apiGatewayStageName}" } 但我无法在 cdk 部署期间获取 restApiId。 “稍后使用 url”是指在堆栈部署的后期,我想用 url 值填充 S3 文件。
  • @benito_h 看看我的回答:stackoverflow.com/a/61580083/9931092
  • @Tim 你能找到解决方案吗?正在考虑使用 url 作为参数创建一个自定义资源来创建该对象,但很想听听是否有更优雅的解决方案。
  • 看到他们正在关闭您的问题 @alex9311,我在 github.com/aws/aws-cdk/issues/12903 提出了一个功能请求,可能更符合 CDK 的愿景

标签: aws-lambda aws-api-gateway aws-cdk


【解决方案1】:

您正在创建一个 LambdaIntegration,但它没有连接到您的 API。

要将其添加到 API 的根目录,请执行以下操作:this.api.root.addMethod(...) 并使用它来连接您的 LambdaIntegration 和 API。

这应该会给你一个带有 URL 的端点

【讨论】:

    【解决方案2】:

    如果您也使用s3-deployment 模块来部署您的网站,我能够使用当前可用的解决方案(在https://github.com/aws/aws-cdk/issues/12903 等待更好的解决方案)。下面一起允许您将config.js 部署到您的存储桶(包含仅在部署时填充的堆栈中的属性),然后您可以在运行时在代码中的其他地方依赖它。

    inline-source.ts:

    // imports removed for brevity
    
    export function inlineSource(path: string, content: string, options?: AssetOptions): ISource {
      return {
        bind: (scope: Construct, context?: DeploymentSourceContext): SourceConfig => {
          if (!context) {
            throw new Error('To use a inlineSource, context must be provided');
          }
          
          // Find available ID
          let id = 1;
          while (scope.node.tryFindChild(`InlineSource${id}`)) {
            id++;
          }
          
          const bucket = new Bucket(scope, `InlineSource${id}StagingBucket`, {
            removalPolicy: RemovalPolicy.DESTROY
          });
          
          const fn = new Function(scope, `InlineSource${id}Lambda`, {
            runtime: Runtime.NODEJS_12_X,
            handler: 'index.handler',
            code: Code.fromAsset('./inline-lambda')
          });
          
          bucket.grantReadWrite(fn);
          
          const myProvider = new Provider(scope, `InlineSource${id}Provider`, {
            onEventHandler: fn,
            logRetention: RetentionDays.ONE_DAY   // default is INFINITE
          });
          
          const resource = new CustomResource(scope, `InlineSource${id}CustomResource`, { serviceToken: myProvider.serviceToken, properties: { bucket: bucket.bucketName, path, content } });
          
          context.handlerRole.node.addDependency(resource); // Sets the s3 deployment to depend on the deployed file
    
          bucket.grantRead(context.handlerRole);
          
          return {
            bucket: bucket,
            zipObjectKey: 'index.zip'
          };
        },
      };
    }
    

    inline-lambda/index.js 中(还需要将归档器安装到 inline-lambda/node_modules 中):

    const aws = require('aws-sdk');
    const s3 = new aws.S3({ apiVersion: '2006-03-01' });
    const fs = require('fs');
    var archive = require('archiver')('zip');
    
    exports.handler = async function(event, ctx) {
      await new Promise(resolve => fs.unlink('/tmp/index.zip', resolve));
      
      const output = fs.createWriteStream('/tmp/index.zip');
    
      const closed = new Promise((resolve, reject) => {
        output.on('close', resolve);
        output.on('error', reject);
      });
      
      archive.pipe(output);
      archive.append(event.ResourceProperties.content, { name: event.ResourceProperties.path });
    
      archive.finalize();
      await closed;
    
      await s3.upload({Bucket: event.ResourceProperties.bucket, Key: 'index.zip', Body: fs.createReadStream('/tmp/index.zip')}).promise();
    
      return;
    }
    

    在你的构造中,使用inlineSource:

    export class TestConstruct extends Construct {
      constructor(scope: Construct, id: string, props: any) {
        // set up other resources
        const source = inlineSource('config.js',  `exports.config = { apiEndpoint: '${ api.attrApiEndpoint }' }`);
        // use in BucketDeployment
      }
    }
    

    您可以将inline-lambda 移动到其他地方,但它需要能够作为 lambda 的资产进行捆绑。

    这是通过创建一个自定义资源来实现的,该资源依赖于堆栈中的其他资源(从而允许解析属性),该资源将您的文件写入一个 zip,然后存储到一个存储桶中,然后将其拾取并解压缩到您的部署/目标存储桶中。相当复杂,但可以利用当前可用的东西完成工作。

    【讨论】:

      【解决方案3】:

      我成功使用的模式是将 CloudFront 分配或 API 网关放在 S3 存储桶前面。

      所以对https://[api-gw]/**/* 的请求被代理到https://[s3-bucket]/**/*

      然后我将在同一个 API 网关中创建一个新的代理路径,用于名为 /config 的路由,它是一个标准的 Lambda 支持的 API 端点,我可以在其中将品牌信息或 API 密钥等各种东西返回到前端,每当前端调用GET /config

      此外,这还避免了诸如 CORS 之类的问题,因为两个来源是相同的(API 网关域)。

      使用 CloudFront 分发而不是 API 网关,它几乎相同,只是您使用 CloudFront 分发的“来源”配置而不是路径和方法。

      【讨论】:

        【解决方案4】:

        处理此问题的正确方法是使用您的 API url 创建一个 CfnOutput,如下所示:

        new cdk.CfnOutput(this, 'apiUrl', {
            value: this.api.url!,
        });
        

        【讨论】:

        • 问题是作者想在同一个栈中使用那个值,使用一个输出会需要你使用多个栈
        猜你喜欢
        • 1970-01-01
        • 2018-03-22
        • 2023-02-04
        • 2022-01-03
        • 2021-03-15
        • 2021-05-11
        • 2021-02-18
        • 1970-01-01
        • 2020-08-12
        相关资源
        最近更新 更多