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

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

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

S3 静态站点中的 JavaScript 调用 lambda 来填充 HTML 列表,但它需要知道 lambda 集成的 API 网关 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 部署上,apiUrl =

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

因此,直到静态站点需要该值之后,才会解析/生成 url。

如何计算/查找/获取 API 网关 URL 并更新 cdk 部署上的 javascript?

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

谢谢。

aws-lambda aws-api-gateway aws-cdk
4个回答
1
投票

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

因此对

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

然后,我将在同一个 API 网关中为名为

/config
的路由创建一个新的代理路径,这是一个标准 Lambda 支持的 API 端点,我可以在其中向前端返回各种信息,例如品牌信息或 API 密钥,每当前端调用
GET /config
.

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

使用 CloudFront 发行版而不是 API 网关,几乎是一样的,只不过您使用 CloudFront 发行版的“源”配置而不是路径和方法。


0
投票

您正在创建 LambdaIntegration,但它未连接到您的 API。

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

这应该为您提供一个带有 URL 的端点


0
投票

如果您也使用

s3-deployment
模块来部署您的网站,我可以使用当前可用的内容编写一个解决方案(等待更好的解决方案在 https://github.com/aws/aws-cdk /问题/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,然后将其存储到存储桶中,然后将其拾取并解压缩到您的部署/目标存储桶。相当复杂,但可以用当前可用的东西完成工作。


0
投票

有几种不同的方法来处理这个问题,但最优雅的方法(在我看来)是将 CloudFront 放在静态网站前面,并为静态网站能够使用的 API 设置反向代理作为相对路径。

我在这里整理了一个这样的例子,并在这里写了解决方案的详细说明,但为了最简短的总结:

  1. 为您的静态网站创建一个存储桶。
  2. 为您的 lambda 创建 API Gateway API。
  3. 配置具有两个源(S3 存储桶和 API 端点)的 CloudFront 分配,并具有“附加”行为,将所有请求(除主机头之外的所有请求)重定向到 API 路径(例如
    api/*
    )到 API终点。

关于上面第 (2) 部分的注释 - 对于像 CDK 这样强大的解决方案来说,这是一个非常奇怪的要求 - 目前不可能直接从 API 网关构造(例如

RestApi
)获取 API 的 URL,所以你'你需要自己重建它:

const siteApi = new RestApi(stack, 'site-api');

...

const apiOriginName = `${siteApi.restApiId}.execute-api.${stack.region}.amazonaws.com`;
© www.soinside.com 2019 - 2024. All rights reserved.