前言
之前有出现过一种情况,就是我们的静态站点是有国内和国外加速的(这个是在 aws 的 router 53 服务配置的),比如:
- 如果是国内访问的,那么就引导到腾讯云的 COS 存储服务 + CDN 服务
- 如果是国外访问的,那么就引导到 aws 的 S3 存储服务 + Cloudfront CDN 服务
正常的文件当然没问题,但是有时候会出现如果 url 后面不输入 index.html,直接子目录斜杠结尾,比如 https://m.foo.com/cast/
,这时候腾讯云表现是正常的,因为他会索引到 https://m.foo.com/cast/index.html
, 但是 aws 的 cloudfront 就不行,会直接报错, 显示获取 S3 对象失败:1
2
3[kbz@VM_16_9_centos ~]$ curl https://m.foo.com/cast/
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>QABCT7FCM4M3T4AR</RequestId><HostId>VnPLeZeR2KQkIMDrFhukmVLUGp+RBWbZO/V8qHeS0dGKLa1N8KOTbWvwNW/J2NKbQWqQXyP5wdw=</HostId></Error>
更有甚者,如果我连子目录的斜杠都没有写全,直接子目录的话,比如 https://m.foo.com/cast
, 这时候腾讯云的表现也是对的,他如果找不到 cast 这个文件的话,就会将其当做一个子目录,再去检索这个子目录下的 index.html 文件。所以你用 curl 请求的话,会返回一个 302 跳转,然后 location 头部就会带上 https://m.foo.com/cast/
, 这时候就会跳转到正确的页面:
1 | [kbz@centos156 ~]$ curl -I https://m.foo.com/cast |
如果是换成 aws 的 cloudfront 的话,那肯定就是又找不到文件了:1
2
3[kbz@VM_16_9_centos ~]$ curl https://m.foo.com/cast
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>52F9V25GCXQYVZ0V</RequestId><HostId>bk0YnUOx7fQz3mwqGCA84hXZXuLUj8Tb2YjH6DNhd+SLu75csLPDfstR5qRksNKZldPvqh2Hst4=</HostId></Error>
解决方式
那么要怎么解决呢,让其 Cloudfront 的跳转规则跟 腾讯云 的跳转规则一致??
首先我们要知道,其实对于根目录的默认索引文件, Cloudfront 是允许设置的。
也就是说,我如果输入 https://m.foo.com
, 那么是会索引到 https://m.foo.com/index.html
这个是没问题的。
但是他不适合 子目录 的方式,这个是他文档的描述:
CloudFront does allow you to specify a default root object (index.html), but it only works on the root of the website (such as http://www.example.com > http://www.example.com/index.html). It does not work on any subdirectory (such as http://www.example.com/about/). If you were to attempt to request this URL through CloudFront, CloudFront would do a S3 GetObject API call against a key that does not exist.
也就是说,我如果请求
http://www.example.com/about/
, 那么 cloudfront 只会老老实实的去 S3 bucket 去取这个对象,而不会去找这个子目录默认的索引文件 index.html
不过他有给了我们一个解决方法,就是用 Lambda 的边缘计算能力,针对 cloudfront 的请求进行重写, 从而让 cloudfront 在 S3 bucket 找到对应的文件。
具体操作文档: 使用Lambda @ Edge在Amazon S3支持的Amazon CloudFront起源中实施默认目录索引
具体实操
对于 Lambda 以及跟 S3 和 Cloudfront 的联动使用,在我以往的文章说了很多次了,具体可以看:
- web 安全之 - cloudfront 添加 x-frame-options 防止 Clickjacking
- 针对国内和国外的图片资源和缩略图做CDN优化
- 项目使用 aws 的 lambda服务来生成s3的缩略图
这边直接进入实操流程:
1. 创建函数
首先 在 lambda 后台创建一个 lambda 函数: m-addsubfolderaccess
2. 写代码,并保存
然后选择 nodejs 版本,我这边选择的是 10.x
, 接下来就开始写代码了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33'use strict';
exports.handler = (event, context, callback) => {
// Extract the request from the CloudFront event that is sent to Lambda@Edge
var request = event.Records[0].cf.request;
// Extract the URI from the request
var olduri = request.uri;
// Match any '/' that occurs at the end of a URI. Replace it with a default index
// 如果最后一个字符是 /, 那么就加上 index.html
var newuri = olduri;
if(olduri[olduri.length - 1] === '/'){
newuri += 'index.html';
}else{
// 获取斜杠的最后一个参数
var uriArr = olduri.split("/");
if(uriArr.length > 1){
var lastPath = uriArr[uriArr.length -1];
// 判断最后一个路径是不是有一个 . 结尾的,比如 a.jpg, 如果有的话,说明是资源。 如果么有的话,那么就是有可能没有加 斜杠的目录
if(lastPath && lastPath.split(".").length === 1){
newuri += '/index.html';
}
}
}
// Log the URI as received by CloudFront and the new URI to be used to fetch from origin
console.log("Old URI: " + olduri);
console.log("New URI: " + newuri);
// Replace the received URI with the URI that includes the index page
request.uri = newuri;
// Return to CloudFront
return callback(null, request);
};
代码很简单,就是针对当前请求的 uri,根据不同的判断进行重写,主要是判断两种情况:
- 如果最后一个字符是
/
, 那么就加上 index.html ,这样子就可以解决子目录没有取默认 index.html 的问题 - 获取最后一个
/
后面的字符,如果不存在 “.” 的字符 (说明不是资源,比如1.jpg
和a.html
, 这边是认为这个站点的合理资源都是以[name].[ext]
的方式存在的,如果没有点,说明是一个子目录), 那么我们就认为最后的 path 其实是一个子目录,然后只需要添加/index.html
就可以了。
代码写好了,接下来点击上面的 Deploy 按钮,表示保存成功
3. 测试一下代码是否正确
代码写好了,接下来我们跑一下测试是否正确, 首先我们测试第一个逻辑,选择一个 http 的跳转事件,然后将 uri 改成 /cast/
,
然后点击调用:
查看执行结果,打印出来的日志 new uri 符合期望是 /cast/index.html
, 说明没错。
接下来测试第二种情况,将 uri 改成 /cast
:
然后点击调用:
查看执行结果,打印出来的日志 new uri 符合期望是 /cast/index.html
, 说明没错。
4. 应用到 cloudfront
测试结果正常。接下来我们就要应用到 cloudfront 了, 选择 右上角的 [操作] - [部署到 Lambda@Edge]
选择你想要的这个 cloudfront (一定要选对,不要应用错了), 然后选择 源请求
事件, 因为我们要对请求的 uri 进行重写, 然后点击部署。
这时候这个 cloudfront 就会重新部署。 大概 5 分钟就可以看效果了。
而且部署的同时,lambda 也会帮我们将这个函数,生成一个新的版本
5. 测试有没有部署生效
等部署完之后,我们就可以重新请求了, 这时候用 curl 请求一下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[kbz@VM_16_9_centos ~]$ curl -I https://m.foo.com/cast/
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 10754
Connection: keep-alive
Date: Thu, 25 Mar 2021 08:05:34 GMT
Last-Modified: Thu, 28 Jan 2021 06:18:46 GMT
ETag: "93ff430a043037a9452fe191f6b0320b"
Accept-Ranges: bytes
Server: AmazonS3
Strict-Transport-Security: max-age=7776000
X-Cache: Hit from cloudfront
Via: 1.1 cfa15842f57761e1aba6ea8338d380d5.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: SFO20-C1
X-Amz-Cf-Id: 5oaotoHwXoQT197vrn5ORM1dICkGrgAH-5ILwrDcv0ru9bA1VdczRg==
Age: 2521
这时候就是 200 成功,并且有 hit cloudfront 了。 去掉 最后的 /
再试试:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[kbz@VM_16_9_centos ~]$ curl -I https://m.foo.com/cast
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 10754
Connection: keep-alive
Date: Thu, 25 Mar 2021 08:05:38 GMT
Last-Modified: Thu, 28 Jan 2021 06:18:46 GMT
ETag: "93ff430a043037a9452fe191f6b0320b"
Accept-Ranges: bytes
Server: AmazonS3
Strict-Transport-Security: max-age=7776000
X-Cache: Hit from cloudfront
Via: 1.1 88734c1b1a8053ae83daf0f85731c788.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: SFO20-C1
X-Amz-Cf-Id: U6de8MCZwtoS6zyb4lLgDjZCsUkeJa4n_xW9PXLUb8rBL1K-vGvolg==
Age: 2451
说明成功生效了。
6. 代码迭代重新部署要注意
因为有时候我们会需要更新代码,这时候就会有代码版本迭代,并且重新应用部署的情况。
这时候要注意一个细节,就是要把旧版本代码所关联的触发器先删掉, 然后再为新版本的代码,创建一个新的触发器(跟旧的触发器一样的配置即可)并进行关联,最后再部署。
否则就会出现还是会应用之前有绑定触发器的那个版本的代码,也就是并没有应用到新版本代码,导致一直都有缓存。
直接配置 cloudfront 的源域属性
除了上面的 lambda 方式之后, 后面还发现了一种更简单的方式可以获取子目录的根文件
之前之所以不行是因为我们在配置 cloudfront 的源域的时候,选择是 s3 作为存储桶的源域名,比如
这时候 cloudfront 会提示你是否要将这个源域设置成 S3 存储桶
, 还是要设置为 S3 网站端点
, 这两种的域名是不一样的,而且效果也不一样。
如果只是当做云端的存储桶,供用户下载文件, 那么就设置为 S3 存储桶
,这时候 cloudfront 只有准确匹配到路径,才会获取文件。
如果是当做站点来访问的话,那么就要设置为 S3 网站端点
, 这时候 cloudfront 在遇到子目录检索的时候,对于缺省的文件路径,是会默认获取根目录文件的,比如 index.html
这种。
因此对于本例来说,肯定是要设置为 S3 网站端点
才行,也就是点击下面的 使用网站端点
这样子就可以了,就可以访问子目录的根文件了。
参考资料