[英]Modify Cloudfront origin response with Lambda - read-only headers
I have a Cloudfront distribution with a single React site, which is hosting in S3.我有一个带有单个 React 站点的 Cloudfront 发行版,该站点托管在 S3 中。 The origin is connected via REST api.
原点通过 REST api 连接。 To properly handle queries, I use custom error responses on status 403 and 404 to 200 and route them to root.
为了正确处理查询,我对状态 403 和 404 到 200 使用自定义错误响应,并将它们路由到 root。 The root object is index.html and everything seems to be fine.
根 object 是 index.html ,一切似乎都很好。
Now I have a task to add to a distribution an another site, which should be accessible through a subdirectory.现在我的任务是向分发中添加另一个站点,该站点应该可以通过子目录访问。 To do this I have to set a root object for a subdirectory and to catch 404 and 403 responses and transfer them to a root object.
为此,我必须为子目录设置根 object 并捕获 404 和 403 响应并将它们传输到根 object。 I've already set up origin and behaviour.
我已经设置了起源和行为。
I tried to use theese manuals: example source but it seems that something went wrong我尝试使用这些手册: 示例源,但似乎出了点问题
The first approach (CloudFrontSubdirectoryIndex) seems not working at all (the function is not invoked and no rewrite happens), so i tried CloudFront function and it seems to work fine.第一种方法(CloudFrontSubdirectoryIndex)似乎根本不起作用(没有调用 function,也没有发生重写),所以我尝试了CloudFront function ,它似乎工作正常。
The last step is to handle 404 and 403 responses.最后一步是处理 404 和 403 响应。 Here is the function from the manual:
这是手册中的 function:
'use strict';
const http = require('https');
const indexPage = 'index.html';
exports.handler = async (event, context, callback) => {
const cf = event.Records[0].cf;
const request = cf.request;
const response = cf.response;
const statusCode = response.status;
// Only replace 403 and 404 requests typically received
// when loading a page for a SPA that uses client-side routing
const doReplace = request.method === 'GET'
&& (statusCode == '403' || statusCode == '404');
const result = doReplace
? await generateResponseAndLog(cf, request, indexPage)
: response;
callback(null, result);
};
async function generateResponseAndLog(cf, request, indexPage){
const domain = cf.config.distributionDomainName;
const appPath = getAppPath(request.uri);
const indexPath = `/${appPath}/${indexPage}`;
const response = await generateResponse(domain, indexPath);
console.log('response: ' + JSON.stringify(response));
return response;
}
async function generateResponse(domain, path){
try {
// Load HTML index from the CloudFront cache
const s3Response = await httpGet({ hostname: domain, path: path });
const headers = s3Response.headers ||
{
'content-type': [{ value: 'text/html;charset=UTF-8' }]
};
return {
status: '200',
headers: wrapAndFilterHeaders(headers),
body: s3Response.body
};
} catch (error) {
return {
status: '500',
headers:{
'content-type': [{ value: 'text/plain' }]
},
body: 'An error occurred loading the page'
};
}
}
function httpGet(params) {
return new Promise((resolve, reject) => {
http.get(params, (resp) => {
console.log(`Fetching ${params.hostname}${params.path}, status code : ${resp.statusCode}`);
let result = {
headers: resp.headers,
body: ''
};
resp.on('data', (chunk) => { result.body += chunk; });
resp.on('end', () => { resolve(result); });
}).on('error', (err) => {
console.log(`Couldn't fetch ${params.hostname}${params.path} : ${err.message}`);
reject(err, null);
});
});
}
// Get the app path segment e.g. candidates.app, employers.client etc
function getAppPath(path){
if(!path){
return '';
}
if(path[0] === '/'){
path = path.slice(1);
}
const segments = path.split('/');
// will always have at least one segment (may be empty)
return segments[0];
}
// Cloudfront requires header values to be wrapped in an array
function wrapAndFilterHeaders(headers){
const allowedHeaders = [
'content-type',
'content-length',
'last-modified',
'date',
'etag'
];
const responseHeaders = {};
if(!headers){
return responseHeaders;
}
for(var propName in headers) {
// only include allowed headers
if(allowedHeaders.includes(propName.toLowerCase())){
var header = headers[propName];
if (Array.isArray(header)){
// assume already 'wrapped' format
responseHeaders[propName] = header;
} else {
// fix to required format
responseHeaders[propName] = [{ value: header }];
}
}
}
return responseHeaders;
}
When i try to implement this solution (attach the function to origin response) I get当我尝试实施此解决方案时(将 function 附加到原始响应)我得到
The Lambda function result failed validation: The function tried to add, delete, or change a read-only header.
Lambda function 结果验证失败:function 尝试添加、删除或更改只读 Z09945EZ6953404DBF31。
Here is a list of restricted headers, but I'm not modifying any of them. 这是一个受限制的标题列表,但我没有修改任何一个。 If I try not to attach any headers to a response at all, the message is the same.
如果我尝试根本不将任何标题附加到响应中,则消息是相同的。 If I try to attach all headers, CloudFront says that i'm modifying a black-listed header.
如果我尝试附加所有标题,CloudFront 会说我正在修改列入黑名单的 header。 Objects in a bucket have only one customized Cache-Control: no-cache metadata.
Bucket 中的对象只有一个自定义的 Cache-Control:no-cache 元数据。
It seemed to be a fast task, but I'm stuck for two days already.这似乎是一项快速的任务,但我已经被困了两天了。 Any help will be appreciated.
任何帮助将不胜感激。
UPD: I've searched the logs and found UPD:我搜索了日志并发现
ERROR Validation error: Lambda function result failed validation, the function tried to delete read-only header, headerName: Transfer-Encoding.
ERROR Validation error: Lambda function result failed validation, the function tried to delete read-only header, headerName: Transfer-Encoding.
I'm a little bit confused.我有点困惑。 This header is not present in origin response, but CF is telling that I deleted it...
这个 header 在原始响应中不存在,但 CF 告诉我我删除了它......
I tried to find the value of the header "Transfer-Encoding" that should come from origin (S3) but it seems that it has been disappeared.我试图找到应该来自原点(S3)的 header“传输编码”的值,但它似乎已经消失了。 And CloudFront says that this header is essential.
CloudFront 说这个 header 是必不可少的。
So I've just hard-coded it and everything becomes fine.所以我只是对其进行了硬编码,一切都变得很好。
'use strict';
const http = require('https');
const indexPage = 'index.html';
exports.handler = async (event, context, callback) => {
const cf = event.Records[0].cf;
const request = cf.request;
const response = cf.response;
const statusCode = response.status;
// Only replace 403 and 404 requests typically received
// when loading a page for a SPA that uses client-side routing
const doReplace = request.method === 'GET'
&& (statusCode == '403' || statusCode == '404');
const result = doReplace
? await generateResponseAndLog(cf, request, indexPage)
: response;
callback(null, result);
};
async function generateResponseAndLog(cf, request, indexPage){
const domain = cf.config.distributionDomainName;
const appPath = getAppPath(request.uri);
const indexPath = `/${appPath}/${indexPage}`;
const response = await generateResponse(domain, indexPath);
console.log('response: ' + JSON.stringify(response));
return response;
}
async function generateResponse(domain, path){
try {
// Load HTML index from the CloudFront cache
const s3Response = await httpGet({ hostname: domain, path: path });
const headers = s3Response.headers ||
{
'content-type': [{ value: 'text/html;charset=UTF-8' }]
};
s3Response.headers['transfer-encoding'] = 'chunked';
return {
status: '200',
headers: wrapAndFilterHeaders(headers),
body: s3Response.body
};
} catch (error) {
return {
status: '500',
headers:{
'content-type': [{ value: 'text/plain' }]
},
body: 'An error occurred loading the page'
};
}
}
function httpGet(params) {
return new Promise((resolve, reject) => {
http.get(params, (resp) => {
console.log(`Fetching ${params.hostname}${params.path}, status code : ${resp.statusCode}`);
let result = {
headers: resp.headers,
body: ''
};
resp.on('data', (chunk) => { result.body += chunk; });
resp.on('end', () => { resolve(result); });
}).on('error', (err) => {
console.log(`Couldn't fetch ${params.hostname}${params.path} : ${err.message}`);
reject(err, null);
});
});
}
// Get the app path segment e.g. candidates.app, employers.client etc
function getAppPath(path){
if(!path){
return '';
}
if(path[0] === '/'){
path = path.slice(1);
}
const segments = path.split('/');
// will always have at least one segment (may be empty)
return segments[0];
}
// Cloudfront requires header values to be wrapped in an array
function wrapAndFilterHeaders(headers){
const allowedHeaders = [
'content-type',
'content-length',
'content-encoding',
'transfer-encoding',
'last-modified',
'date',
'etag'
];
const responseHeaders = {};
if(!headers){
return responseHeaders;
}
for(var propName in headers) {
// only include allowed headers
if(allowedHeaders.includes(propName.toLowerCase())){
var header = headers[propName];
if (Array.isArray(header)){
// assume already 'wrapped' format
responseHeaders[propName] = header;
} else {
// fix to required format
responseHeaders[propName] = [{ value: header }];
}
}
}
return responseHeaders;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.