AWS Cloudfront for S3 支持的网站 + Rest API:(错误 - MethodNotAllowed / 指定的方法不允许针对此资源)
AWS Cloudfront for S3 backed website + Rest API: (Error - MethodNotAllowed / The specified method is not allowed against this resource)
我有一个 AWS S3 支持的静态网站和一个 RestApi。我正在为静态网站和 RestApi 配置单个 Cloudfront Distribution。我已经为 S3 来源和 RestApi 来源完成了 OriginConfigs。我正在使用 AWS CDK 在代码中定义基础设施。
采用了这篇文章的方法:https://dev.to/evnz/single-cloudfront-distribution-for-s3-web-app-and-api-gateway-15c3]
API定义在相对路径/r/<resourcename>
或/r/api/<methodname>
下。例如,/r/Account
引用帐户资源,/r/api/Validate
引用名为 Validate 的 rpc 样式方法(在本例中为 HTTP POST 方法)。实现资源方法的 Lambda 方法配置了适当的 PREFLIGHT OPTIONS,静态网站的 url 列在该资源的允许来源中。例如:/r/api/Validate
方法 lambda 具有
exports.main = async function(event, context) {
try {
var method = event.httpMethod;
if(method === "OPTIONS") {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "*",
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Origin": website_url,
"Vary": "Origin",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE"
}
};
return response;
} else if(method === "POST") {
...
}
...
}
API 和网站部署正常。这是 CDK 部署代码片段。
const string api_domain = "myrestapi.execute-api.ap-south-1.amazonaws.com";
const string api_stage = "prod";
internal WebAppStaticWebsiteStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
{
// The S3 bucket to hold the static website contents
var bucket = new Bucket(this, "WebAppStaticWebsiteBucket", new BucketProps {
PublicReadAccess = false,
BlockPublicAccess = BlockPublicAccess.BLOCK_ALL,
RemovalPolicy = RemovalPolicy.DESTROY,
WebsiteIndexDocument = "index.html",
Cors = new ICorsRule[] {
new CorsRule() {
AllowedHeaders = new string[] { "*" },
AllowedMethods = new HttpMethods[] { HttpMethods.GET, HttpMethods.POST, HttpMethods.PUT, HttpMethods.DELETE, HttpMethods.HEAD },
AllowedOrigins = new string[] { "*" }
}
}
});
var cloudfrontOAI = new OriginAccessIdentity(this, "CloudfrontOAI", new OriginAccessIdentityProps() {
Comment = "Allows cloudfront access to S3"
});
bucket.AddToResourcePolicy(new PolicyStatement(new PolicyStatementProps() {
Sid = "Grant cloudfront origin access identity access to s3 bucket",
Actions = new [] { "s3:GetObject" },
Resources = new [] { bucket.BucketArn + "/*" },
Principals = new [] { cloudfrontOAI.GrantPrincipal }
}));
// The cloudfront distribution for the website
var distribution = new CloudFrontWebDistribution(this, "WebAppStaticWebsiteDistribution", new CloudFrontWebDistributionProps() {
ViewerProtocolPolicy = ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
DefaultRootObject = "index.html",
PriceClass = PriceClass.PRICE_CLASS_ALL,
GeoRestriction = GeoRestriction.Whitelist(new [] {
"IN"
}),
OriginConfigs = new [] {
new SourceConfiguration() {
CustomOriginSource = new CustomOriginConfig() {
OriginProtocolPolicy = OriginProtocolPolicy.HTTPS_ONLY,
DomainName = api_domain,
AllowedOriginSSLVersions = new OriginSslPolicy[] { OriginSslPolicy.TLS_V1_2 },
},
Behaviors = new IBehavior[] {
new Behavior() {
IsDefaultBehavior = false,
PathPattern = $"/{api_stage}/r/*",
AllowedMethods = CloudFrontAllowedMethods.ALL,
CachedMethods = CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
DefaultTtl = Duration.Seconds(0),
ForwardedValues = new CfnDistribution.ForwardedValuesProperty() {
QueryString = true,
Headers = new string[] { "Authorization" }
}
}
}
},
new SourceConfiguration() {
S3OriginSource = new S3OriginConfig() {
S3BucketSource = bucket,
OriginAccessIdentity = cloudfrontOAI
},
Behaviors = new [] {
new Behavior() {
IsDefaultBehavior = true,
//PathPattern = "/*",
DefaultTtl = Duration.Seconds(0),
Compress = false,
AllowedMethods = CloudFrontAllowedMethods.ALL,
CachedMethods = CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS
}
},
}
}
});
// The distribution domain name - output
var domainNameOutput = new CfnOutput(this, "WebAppStaticWebsiteDistributionDomainName", new CfnOutputProps() {
Value = distribution.DistributionDomainName
});
// The S3 bucket deployment for the website
var deployment = new BucketDeployment(this, "WebAppStaticWebsiteDeployment", new BucketDeploymentProps(){
Sources = new [] {Source.Asset("./website/dist")},
DestinationBucket = bucket,
Distribution = distribution
});
}
我遇到以下错误(从浏览器控制台错误日志中提取):
bundle.js:67 POST https://mywebapp.cloudfront.net/r/api/Validate 405
bundle.js:67
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>MethodNotAllowed</Code>
<Message>The specified method is not allowed against this resource.</Message>
<Method>POST</Method>
<ResourceType>OBJECT</ResourceType>
<RequestId>xxxxx</RequestId>
<HostId>xxxxxxxxxxxxxxx</HostId>
</Error>
预期的流程是 POST 对 https://mywebapp.cloudfront.net/r/api/Validate 的调用(使用 fetch() api 进行)由云端转发到 RestApi 后端。看起来 Cloudfront 正在执行此操作,但后端正在返回错误(根据错误消息)。
我错过了什么?我该怎么做?
已通过执行以下操作解决此问题:
- 移至 Distribution 构造(根据 AWS 文档,它是接收最新更新时使用的构造)。
- 添加 CachePolicy 和 OriginRequestPolicy 来控制 Cookie 转发和 Header 转发
我有一个 AWS S3 支持的静态网站和一个 RestApi。我正在为静态网站和 RestApi 配置单个 Cloudfront Distribution。我已经为 S3 来源和 RestApi 来源完成了 OriginConfigs。我正在使用 AWS CDK 在代码中定义基础设施。
采用了这篇文章的方法:https://dev.to/evnz/single-cloudfront-distribution-for-s3-web-app-and-api-gateway-15c3]
API定义在相对路径/r/<resourcename>
或/r/api/<methodname>
下。例如,/r/Account
引用帐户资源,/r/api/Validate
引用名为 Validate 的 rpc 样式方法(在本例中为 HTTP POST 方法)。实现资源方法的 Lambda 方法配置了适当的 PREFLIGHT OPTIONS,静态网站的 url 列在该资源的允许来源中。例如:/r/api/Validate
方法 lambda 具有
exports.main = async function(event, context) {
try {
var method = event.httpMethod;
if(method === "OPTIONS") {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "*",
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Origin": website_url,
"Vary": "Origin",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE"
}
};
return response;
} else if(method === "POST") {
...
}
...
}
API 和网站部署正常。这是 CDK 部署代码片段。
const string api_domain = "myrestapi.execute-api.ap-south-1.amazonaws.com";
const string api_stage = "prod";
internal WebAppStaticWebsiteStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
{
// The S3 bucket to hold the static website contents
var bucket = new Bucket(this, "WebAppStaticWebsiteBucket", new BucketProps {
PublicReadAccess = false,
BlockPublicAccess = BlockPublicAccess.BLOCK_ALL,
RemovalPolicy = RemovalPolicy.DESTROY,
WebsiteIndexDocument = "index.html",
Cors = new ICorsRule[] {
new CorsRule() {
AllowedHeaders = new string[] { "*" },
AllowedMethods = new HttpMethods[] { HttpMethods.GET, HttpMethods.POST, HttpMethods.PUT, HttpMethods.DELETE, HttpMethods.HEAD },
AllowedOrigins = new string[] { "*" }
}
}
});
var cloudfrontOAI = new OriginAccessIdentity(this, "CloudfrontOAI", new OriginAccessIdentityProps() {
Comment = "Allows cloudfront access to S3"
});
bucket.AddToResourcePolicy(new PolicyStatement(new PolicyStatementProps() {
Sid = "Grant cloudfront origin access identity access to s3 bucket",
Actions = new [] { "s3:GetObject" },
Resources = new [] { bucket.BucketArn + "/*" },
Principals = new [] { cloudfrontOAI.GrantPrincipal }
}));
// The cloudfront distribution for the website
var distribution = new CloudFrontWebDistribution(this, "WebAppStaticWebsiteDistribution", new CloudFrontWebDistributionProps() {
ViewerProtocolPolicy = ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
DefaultRootObject = "index.html",
PriceClass = PriceClass.PRICE_CLASS_ALL,
GeoRestriction = GeoRestriction.Whitelist(new [] {
"IN"
}),
OriginConfigs = new [] {
new SourceConfiguration() {
CustomOriginSource = new CustomOriginConfig() {
OriginProtocolPolicy = OriginProtocolPolicy.HTTPS_ONLY,
DomainName = api_domain,
AllowedOriginSSLVersions = new OriginSslPolicy[] { OriginSslPolicy.TLS_V1_2 },
},
Behaviors = new IBehavior[] {
new Behavior() {
IsDefaultBehavior = false,
PathPattern = $"/{api_stage}/r/*",
AllowedMethods = CloudFrontAllowedMethods.ALL,
CachedMethods = CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
DefaultTtl = Duration.Seconds(0),
ForwardedValues = new CfnDistribution.ForwardedValuesProperty() {
QueryString = true,
Headers = new string[] { "Authorization" }
}
}
}
},
new SourceConfiguration() {
S3OriginSource = new S3OriginConfig() {
S3BucketSource = bucket,
OriginAccessIdentity = cloudfrontOAI
},
Behaviors = new [] {
new Behavior() {
IsDefaultBehavior = true,
//PathPattern = "/*",
DefaultTtl = Duration.Seconds(0),
Compress = false,
AllowedMethods = CloudFrontAllowedMethods.ALL,
CachedMethods = CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS
}
},
}
}
});
// The distribution domain name - output
var domainNameOutput = new CfnOutput(this, "WebAppStaticWebsiteDistributionDomainName", new CfnOutputProps() {
Value = distribution.DistributionDomainName
});
// The S3 bucket deployment for the website
var deployment = new BucketDeployment(this, "WebAppStaticWebsiteDeployment", new BucketDeploymentProps(){
Sources = new [] {Source.Asset("./website/dist")},
DestinationBucket = bucket,
Distribution = distribution
});
}
我遇到以下错误(从浏览器控制台错误日志中提取):
bundle.js:67 POST https://mywebapp.cloudfront.net/r/api/Validate 405
bundle.js:67
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>MethodNotAllowed</Code>
<Message>The specified method is not allowed against this resource.</Message>
<Method>POST</Method>
<ResourceType>OBJECT</ResourceType>
<RequestId>xxxxx</RequestId>
<HostId>xxxxxxxxxxxxxxx</HostId>
</Error>
预期的流程是 POST 对 https://mywebapp.cloudfront.net/r/api/Validate 的调用(使用 fetch() api 进行)由云端转发到 RestApi 后端。看起来 Cloudfront 正在执行此操作,但后端正在返回错误(根据错误消息)。
我错过了什么?我该怎么做?
已通过执行以下操作解决此问题:
- 移至 Distribution 构造(根据 AWS 文档,它是接收最新更新时使用的构造)。
- 添加 CachePolicy 和 OriginRequestPolicy 来控制 Cookie 转发和 Header 转发