AWS CDK 将 API Gateway URL 传递到同一堆栈中的静态站点

Posted

技术标签:

【中文标题】AWS CDK 将 API Gateway URL 传递到同一堆栈中的静态站点【英文标题】:AWS CDK passing API Gateway URL to static site in same Stack 【发布时间】: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 看看我的回答:***.com/a/61580083/9931092 @Tim 你能找到解决方案吗?正在考虑使用 url 作为参数创建一个自定义资源来创建该对象,但很想听听是否有更优雅的解决方案。 看到他们正在关闭您的问题 @alex9311,我在 github.com/aws/aws-cdk/issues/12903 提出了一个功能请求,可能更符合 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$idStagingBucket`, 
        removalPolicy: RemovalPolicy.DESTROY
      );
      
      const fn = new Function(scope, `InlineSource$idLambda`, 
        runtime: Runtime.NODEJS_12_X,
        handler: 'index.handler',
        code: Code.fromAsset('./inline-lambda')
      );
      
      bucket.grantReadWrite(fn);
      
      const myProvider = new Provider(scope, `InlineSource$idProvider`, 
        onEventHandler: fn,
        logRetention: RetentionDays.ONE_DAY   // default is INFINITE
      );
      
      const resource = new CustomResource(scope, `InlineSource$idCustomResource`,  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!,
);

【讨论】:

问题是作者想在同一个栈中使用那个值,使用一个输出会需要你使用多个栈

以上是关于AWS CDK 将 API Gateway URL 传递到同一堆栈中的静态站点的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 OpenAPI 为 AWS API Gateway 配置 CORS?

使用 AWS API Gateway 访问原始 URL

使用 AWS CDK 为 AWS API 网关启用 CORS

创建请求正文和模板 API GATEWAY CDK

AWS - 使用 @connections websocket 回调 url 从后端发送响应(单向) - API Gateway websocket 协议

如何创建在 Python CDK 中引用自身的 API Gateway 资源策略?