我正在尝试在单个堆栈中部署 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?
谢谢。
我成功使用的模式是将 CloudFront 发行版或 API 网关放在 S3 存储桶前面。
因此对
https://[api-gw]/**/*
的请求将被代理到 https://[s3-bucket]/**/*
。
然后,我将在同一个 API 网关中为名为
/config
的路由创建一个新的代理路径,这是一个标准 Lambda 支持的 API 端点,我可以在其中向前端返回各种信息,例如品牌信息或 API 密钥,每当前端调用 GET /config
.
此外,这还避免了诸如 CORS 之类的问题,因为两个源都是相同的(API 网关域)。
使用 CloudFront 发行版而不是 API 网关,几乎是一样的,只不过您使用 CloudFront 发行版的“源”配置而不是路径和方法。
您正在创建 LambdaIntegration,但它未连接到您的 API。
要将其添加到 API 的根目录,请执行以下操作:this.api.root.addMethod(...) 并使用它来连接您的 LambdaIntegration 和 API。
这应该为您提供一个带有 URL 的端点
如果您也使用
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,然后将其存储到存储桶中,然后将其拾取并解压缩到您的部署/目标存储桶。相当复杂,但可以用当前可用的东西完成工作。
有几种不同的方法来处理这个问题,但最优雅的方法(在我看来)是将 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`;