Compare commits

...

227 Commits

Author SHA1 Message Date
刘祥超
6c2d488c37 版本号修改为1.3.3 2024-01-21 16:58:24 +08:00
刘祥超
d84b844e53 优化测试用例 2024-01-21 11:13:30 +08:00
刘祥超
095c381ae5 WAF允许动作默认跳过所有规则 2024-01-20 20:54:41 +08:00
刘祥超
7d11b3c63b WAF策略增加显示页面动作默认设置 2024-01-20 16:18:07 +08:00
刘祥超
45ba4fe5f1 修复部分内置页面没有<head>标签的问题 2024-01-20 10:14:15 +08:00
刘祥超
ac341da05b 优化代码 2024-01-19 09:27:42 +08:00
刘祥超
12b9c37095 增加edge-node config命令 2024-01-18 10:54:43 +08:00
刘祥超
125b25ea27 如果源站请求响应中没有Content-Type,则不设置Content-Type 2024-01-17 15:17:19 +08:00
刘祥超
6b1d595d58 XSS检测增加测试用例 2024-01-16 21:13:10 +08:00
刘祥超
f26b80e9c1 修改版本号为1.3.2.2 2024-01-16 20:59:25 +08:00
刘祥超
6e1bb43a9e WAF操作符增加“包含SQL注入-严格模式” 2024-01-16 20:41:52 +08:00
刘祥超
35e7ce1435 优化XSS检测的模式 2024-01-16 19:56:04 +08:00
刘祥超
5eb247b999 修复5秒盾验证可能受WAF影响不能工作的问题 2024-01-16 12:03:06 +08:00
刘祥超
c94e93859f 修复Websocket连接无法报告连接关闭的问题 2024-01-16 09:24:51 +08:00
刘祥超
d694319191 优化缓存错误相关代码 2024-01-15 21:00:20 +08:00
刘祥超
84e61f7765 优化代码 2024-01-15 10:31:42 +08:00
刘祥超
196f0612dc 版本号修改为1.3.2.1 2024-01-15 08:40:31 +08:00
刘祥超
4cd5e12686 网站设置增加HLS加密功能(商业版本) 2024-01-14 20:34:55 +08:00
刘祥超
aca128c19d 优化代码 2024-01-13 19:33:29 +08:00
刘祥超
5052d20fbd 字符编码设置增加“强制替换”选项;修复字符编码大写选项不起作用的问题 2024-01-13 16:29:32 +08:00
刘祥超
74790bea4a 提升UA解析性能(2-4倍) 2024-01-12 16:30:32 +08:00
刘祥超
e922c12611 修复缓存策略无法切换文件和内存的问题 2024-01-12 14:17:12 +08:00
刘祥超
cc632d557b 自定义页面跳转支持使用变量 2024-01-11 15:37:56 +08:00
刘祥超
a7dd101dbf 套餐可以设置带宽限制 2024-01-11 15:25:47 +08:00
刘祥超
1e1cd5a643 鉴权访问日志标签增加"auth:"前缀 2024-01-11 10:48:53 +08:00
刘祥超
6888ccc350 优化brotli相关测试用例 2024-01-10 15:33:49 +08:00
刘祥超
8d9a4b6d4f 修复HTTP/3下无法传递ServerAddr的问题 2024-01-10 11:46:24 +08:00
刘祥超
470b33e90f 相关CFLAGS增加优化级别-O2 2024-01-09 19:15:51 +08:00
刘祥超
74a559b1a0 优化WAF验证码输入框和字体样式 2024-01-09 17:11:18 +08:00
刘祥超
9db86b011a WAF人机识别验证后返回后自动跳转到目标页面 2024-01-09 17:00:26 +08:00
刘祥超
c94a51f47b 优化WAF验证码输入界面 2024-01-09 15:39:52 +08:00
刘祥超
2a9ec05d45 在开发模式下重启时不重载临时IP白名单 2024-01-09 11:05:51 +08:00
刘祥超
c9811d78a9 WAF操作符增加“包含XSS注入-严格模式” 2024-01-04 14:54:17 +08:00
刘祥超
65435ab32d 写缓存失败时,允许继续读取源站内容 2023-12-27 20:55:12 +08:00
刘祥超
614c7a4687 优化测试用例 2023-12-25 16:57:25 +08:00
刘祥超
ef541a2d8f 优化计数器性能 2023-12-25 16:41:07 +08:00
刘祥超
9141a1434e edge-node gc命令中的pause时间改为最近一次回收的pause时间 2023-12-25 12:43:13 +08:00
刘祥超
17af07cce0 edge-node gc命令增加耗时和gc pause时长 2023-12-25 09:24:20 +08:00
刘祥超
cfa57fac66 优化计数器相关测试用例 2023-12-24 16:08:57 +08:00
刘祥超
47523eaa73 优化计数器性能 2023-12-24 15:11:09 +08:00
刘祥超
27a24c6a8a 版本号修改为1.3.2 2023-12-24 11:14:45 +08:00
刘祥超
9bc2b1a651 WAF参数中增加“请求来源” 2023-12-24 10:03:24 +08:00
刘祥超
4f24b7f39c 增加Websocket连接数统计 2023-12-20 11:43:00 +08:00
刘祥超
4607a1f4e7 版本号修改为1.3.1.2 2023-12-18 08:51:22 +08:00
刘祥超
0f2068b161 优化TCP源站错误提示 2023-12-15 18:38:09 +08:00
刘祥超
c039691a71 缓存设置中可以设置缓存主域名,用来复用多域名下的缓存 2023-12-13 18:41:51 +08:00
刘祥超
930ee44065 根据系统环境调整WebP转换线程数 2023-12-12 09:55:18 +08:00
刘祥超
8a9aac7d72 优化代码 2023-12-11 20:35:48 +08:00
刘祥超
e50bbb962d WebP策略变化时只更新相关配置 2023-12-11 11:09:12 +08:00
刘祥超
9ff936d0c1 WebP转换质量转移到WebP策略配置 2023-12-11 10:17:17 +08:00
刘祥超
f53727b09c WebP转换限制为单线程,防止占用系统资源过高 2023-12-11 09:33:04 +08:00
刘祥超
525ce1f923 优化WAF XSS检测,减少对图片内容的误判 2023-12-10 19:40:29 +08:00
刘祥超
16e7cd800c WAF SQL注入检测和XSS注入检测自动进行URL解码 2023-12-10 16:52:54 +08:00
刘祥超
3f34bfc0b0 节点进程停止时,自动保存WAF临时白名单,并在进程重新启动后恢复 2023-12-10 15:41:31 +08:00
刘祥超
548cd1002b 增加WAF相关测试用例 2023-12-10 09:27:29 +08:00
刘祥超
3423865868 优化测试用例 2023-12-10 08:54:39 +08:00
刘祥超
037bc8e0de 优化WAF单词匹配性能 2023-12-09 19:19:29 +08:00
刘祥超
e03292de28 WAF规则模板中XSS注入检测规则使用“包含XSS注入”操作符替代以往的正则表达式 2023-12-09 17:00:21 +08:00
刘祥超
ee2565905e 优化WAF动作“显示网页”显示 2023-12-09 15:55:40 +08:00
刘祥超
05881b457d WAF规则模板中SQL注入规则使用“包含SQL注入”操作符替代以往的正则表达式 2023-12-09 15:28:07 +08:00
刘祥超
b116effc6c WAF SQL注入和XSS检测增加缓存/优化部分WAF相关测试用例 2023-12-09 11:46:50 +08:00
刘祥超
536efeeb9c 提升单词匹配性能 2023-12-09 10:06:07 +08:00
刘祥超
e8638e4bec WAF检查项增加“所有报头名称” 2023-12-08 15:39:23 +08:00
刘祥超
c9db722129 WAF增加“包含XSS注入”操作符 2023-12-08 10:15:18 +08:00
刘祥超
90de472bd5 增加测试用例 2023-12-07 20:47:25 +08:00
刘祥超
50c6c60abf WAF SQL注入检测时支持 (http|https):// 开头的URL 2023-12-07 20:38:06 +08:00
刘祥超
cc10372fe1 WAF增加“包含SQL注入”操作符 2023-12-07 20:25:35 +08:00
刘祥超
05c98a0656 修复一处单词错误 2023-12-07 12:14:04 +08:00
刘祥超
1a790fe391 优化代码 2023-12-07 12:07:06 +08:00
刘祥超
7dbd73cb59 优化WAF中前缀和后缀相关操作符性能 2023-12-07 12:05:08 +08:00
刘祥超
4dfa571547 WAF操作符增加包含任一单词、包含所有单词、不包含任一单词 2023-12-07 11:42:59 +08:00
刘祥超
9f77f62308 WAF checkpoint返回值支持[][]byte 2023-12-05 17:18:53 +08:00
刘祥超
facea1ed96 优化代码 2023-12-05 16:28:10 +08:00
刘祥超
e367814db3 内容压缩级别允许为0 2023-12-05 10:48:17 +08:00
刘祥超
3a15408c98 修复缓存命中率统计测试用例 2023-12-03 14:55:09 +08:00
刘祥超
c504b37118 WAF相关跳转不计入统计 2023-12-03 14:41:11 +08:00
刘祥超
74708dc02f 默认不启用内存分片管理 2023-12-03 14:26:51 +08:00
刘祥超
0c097498bb 优化链表相关代码 2023-12-03 11:27:47 +08:00
刘祥超
981c063eff 优化验证码性能 2023-11-30 17:25:41 +08:00
刘祥超
5e35c50113 页面优化增加例外URL和限制URL 2023-11-30 15:48:50 +08:00
刘祥超
e6c2869ff2 增加“极验-行为验”验证码集成支持 2023-11-29 17:00:06 +08:00
刘祥超
358bec2e9b WAF验证码验证后返回时判断是否已通过验证 2023-11-28 20:39:42 +08:00
刘祥超
1cd644f2eb 优化验证码加载方式,减少不必要的图片生成 2023-11-28 18:07:27 +08:00
刘祥超
f783e5c331 将版本号修改为1.3.1 2023-11-23 17:19:41 +08:00
刘祥超
c39b1c794f 修复清空文件索引Map时产生并发异常 2023-11-23 17:14:50 +08:00
刘祥超
2633d43897 增加最大内存用量 2023-11-22 17:03:42 +08:00
刘祥超
88dca006c4 优化日志 2023-11-22 16:44:06 +08:00
刘祥超
98feb26b79 优化brotli压缩和解压缩性能 2023-11-21 20:18:37 +08:00
刘祥超
ac6683e79d GRPC增加Keepalive参数 2023-11-20 09:56:50 +08:00
刘祥超
99d24afbcd 验证码验证不区分访问路径 2023-11-19 15:34:22 +08:00
刘祥超
ba19a9f4c4 减少一些不必要的访问统计 2023-11-19 09:10:37 +08:00
刘祥超
7fea67a2b5 区域封禁支持观察者模式 2023-11-18 15:02:58 +08:00
刘祥超
ecd2e6955e 当SNI无法读取到ServerName时,尝试使用节点IP搜索网站 2023-11-18 12:08:51 +08:00
刘祥超
09d60a3047 优化内存缓存最大值算法 2023-11-17 19:12:24 +08:00
刘祥超
e24f390412 优化人机识别样式 2023-11-16 08:57:20 +08:00
刘祥超
eeacec1a4e 人机识别增加UA记录 2023-11-16 08:44:07 +08:00
刘祥超
30cd6373c5 修复WAF相关单元测试 2023-11-16 08:43:31 +08:00
刘祥超
87a6ab0559 源站支持404内容自动重试其他源站 2023-11-15 19:06:15 +08:00
刘祥超
59f27215d3 使用泛型优化计数器内存 2023-11-15 15:57:41 +08:00
刘祥超
768384dcf0 优化计数器 2023-11-15 15:17:03 +08:00
刘祥超
3b52ac0fd2 WAF人机识别实现点击验证和滑动解锁验证/单个网站可以设置默认的人机识别方式 2023-11-15 15:10:25 +08:00
刘祥超
41343b2264 版本号修改为1.3.0 2023-11-14 14:47:11 +08:00
刘祥超
d084059f04 缓存索引数据库取消最后访问时间,以提升某些查询速度 2023-11-13 21:43:25 +08:00
刘祥超
9253c44ba5 使用utils.CutPrefix代替strings.CutPrefix 2023-11-13 18:17:32 +08:00
刘祥超
ddec0bf2e0 限制请求域名长度不超过253 2023-11-13 17:20:46 +08:00
刘祥超
aeba1805af 限制统计数据中域名长度 2023-11-13 17:07:55 +08:00
刘祥超
ecff37e080 优化计数器代码 2023-11-13 15:11:11 +08:00
刘祥超
d31dac75be 自定义页面增加例外URL和限制URL设置 2023-11-13 10:46:26 +08:00
刘祥超
4571c84102 自定义页面增加“跳转URL”功能 2023-11-10 16:36:35 +08:00
刘祥超
6a9f59bee0 修复访问节点自定义内容可能无法生效的问题 2023-11-10 11:41:45 +08:00
刘祥超
f1951869f1 URL跳转中增加例外域名和仅限域名 2023-11-10 11:06:24 +08:00
刘祥超
cfd4195c0f 读取缓存时可以使用源站的ETag 2023-11-09 18:20:32 +08:00
刘祥超
d793472b42 调整缓存索引数据库缓存尺寸 2023-11-06 22:10:34 +08:00
刘祥超
1e56247b9c 调整缓存索引数据库缓存尺寸 2023-11-06 20:26:57 +08:00
刘祥超
c34a38857a 增加测试用例 2023-11-06 18:36:11 +08:00
刘祥超
57fa7036dc 修复磁盘占用统计计算错误 2023-11-03 11:51:53 +08:00
刘祥超
b8a3ac750f 上传域名统计时,限制域名长度不能超过64位 2023-11-02 17:23:39 +08:00
刘祥超
9d6692db0c 进一步缩短缓存Key临时缓存时间 2023-11-02 14:14:28 +08:00
刘祥超
ad94327226 实现网络数据包相关统计(商业版本) 2023-10-26 17:18:42 +08:00
刘祥超
aee1ff9609 更新库 2023-10-26 09:53:23 +08:00
刘祥超
822e967874 优化文件句柄缓存容量判断 2023-10-17 09:59:04 +08:00
刘祥超
2acf890b8e 限制内存缓存最大容量为系统内存的三分之一 2023-10-16 14:28:07 +08:00
刘祥超
3909695b44 优化代码 2023-10-16 11:48:38 +08:00
刘祥超
d0f420a945 将版本号修改为1.2.10 2023-10-15 13:33:18 +08:00
刘祥超
7618338f38 WAF记录IP动作中IP名单如果为空时,默认为全局黑名单 2023-10-15 09:34:50 +08:00
刘祥超
9b2a704e7f 如果设置的缓存容量比当前磁盘总容量大的时候,自动调整为95%磁盘总容量 2023-10-14 22:05:38 +08:00
刘祥超
630c1ec63b 优化缓存自动清理逻辑 2023-10-13 08:36:11 +08:00
刘祥超
5b93b28690 优化缓存命中率统计采样时长和数量 2023-10-13 08:28:13 +08:00
刘祥超
3aa68b5ffc 对WAF正则缓存增加命中率检查 2023-10-12 20:10:30 +08:00
刘祥超
adb0069c59 优化ttlcache回收速度 2023-10-12 16:03:52 +08:00
刘祥超
211da66226 去除遗留的调试信息 2023-10-12 14:59:12 +08:00
刘祥超
81911c4073 更新依赖库 2023-10-12 08:00:26 +08:00
刘祥超
6ff3230bab 限制文件句柄缓存内存使用 2023-10-11 21:51:05 +08:00
刘祥超
f01eae3590 优化代码 2023-10-11 14:07:13 +08:00
刘祥超
47e8761209 优化WAF正则表达式缓存时间 2023-10-11 12:21:10 +08:00
刘祥超
8449fe7c0b 优化代码 2023-10-11 07:24:02 +08:00
刘祥超
7f3e6ddc65 优化批量删除缓存Key代码,防止列表删除了文件还在 2023-10-11 06:31:35 +08:00
刘祥超
6ca8b6837c 删除过期缓存时使用批量删除 2023-10-10 22:08:42 +08:00
刘祥超
a9969430a3 修复内存缓存无法缓存的问题 2023-10-10 15:23:23 +08:00
刘祥超
7c5c06191d 在缓存写入内存之前检查磁盘是否超出容量 2023-10-10 14:45:14 +08:00
刘祥超
afc533c3e4 清理LFU缓存时日志打印消耗时间/删除缓存分区信息文件前判断文件是否存在 2023-10-10 14:02:45 +08:00
刘祥超
71c891ae14 调整vm.dirty_相关系统参数 2023-10-10 11:30:49 +08:00
刘祥超
e8570bfd09 优化内存缓存碎片GC程序 2023-10-09 18:08:30 +08:00
刘祥超
534d64673f 优化内存缓存相关代码 2023-10-09 12:48:30 +08:00
刘祥超
6bff5c978b 优化一处测试用例 2023-10-09 08:51:03 +08:00
刘祥超
38a214878a 缩短内存缓存索引缓存保留时间 2023-10-09 07:49:21 +08:00
刘祥超
149e04d0e4 优化反向代理相关错误提示 2023-10-08 19:05:09 +08:00
刘祥超
5733d466ca 自动调节系统参数时调整vm.dirty_background_ratio和vm.dirty_ratio 2023-10-08 15:09:00 +08:00
刘祥超
a95e2e3259 将本地数据库同步模式改回OFF 2023-10-08 12:29:54 +08:00
刘祥超
a80a89edf5 优化脆片内存逻辑 2023-10-07 14:56:35 +08:00
刘祥超
b8f7d4110f 删除文件缓存时增加文件系统写计数 2023-10-07 12:37:51 +08:00
刘祥超
00e76a6a09 提升内存缓存的碎片内存复用效率 2023-10-07 11:56:34 +08:00
刘祥超
79fa9d88a1 写文件增加负载保护 2023-10-07 09:39:37 +08:00
刘祥超
c460421279 优化本地数据库相关代码 2023-10-06 20:56:27 +08:00
刘祥超
69e4dd6cfe 本地数据库同步模式从关闭改为NORMAL,以降低损坏概率 2023-10-06 00:49:37 +08:00
刘祥超
b73f0ae2c9 加在文件Hash时加入防无限循环机制 2023-10-05 23:08:40 +08:00
刘祥超
2af577380e 修复查询缓存Hash列表SQL参数占位符错误 2023-10-05 21:23:47 +08:00
刘祥超
89bfdc478f 优化缓存Hash查询速度 2023-10-05 17:40:27 +08:00
刘祥超
b6e8221ac1 优化计数器相关代码 2023-10-05 16:29:02 +08:00
刘祥超
ea6d3d7107 增加计数器容量上限 2023-10-05 13:36:55 +08:00
刘祥超
7d8bdfcd45 优化计数器内存使用(内存用量减少40%) 2023-10-05 13:19:32 +08:00
刘祥超
70fe1b5d2b 合并多个计数器,便于统一的内存控制 2023-10-05 09:45:46 +08:00
刘祥超
a7caf0260a 测试用内存统计增加回调函数 2023-10-05 09:15:17 +08:00
刘祥超
d547793eee 优化代码 2023-10-05 08:41:07 +08:00
刘祥超
d92f27c44b ttlcache支持泛型 2023-10-05 08:28:16 +08:00
刘祥超
8561ff3e2d 文件缓存自动加载热门数据时检查是否有足够的内存空间 2023-10-04 18:13:48 +08:00
刘祥超
0736b20d33 优化计数器内存使用 2023-10-04 16:53:39 +08:00
刘祥超
263acb775b 优化测试用例 2023-10-04 14:56:26 +08:00
刘祥超
7a1fff8504 增加测试用例 2023-10-03 21:41:03 +08:00
刘祥超
5c5adf690f 优化代码 2023-10-03 21:38:45 +08:00
刘祥超
1eca5099df 优化缓存相关代码 2023-10-03 19:02:07 +08:00
刘祥超
5f2ad8b096 优化缓存相关代码 2023-10-03 11:39:28 +08:00
刘祥超
4405cfd405 优化缓存相关代码 2023-10-02 19:48:11 +08:00
刘祥超
2f6aa0c14f 优化缓存列表数据库加载速度 2023-10-02 16:05:42 +08:00
刘祥超
bb50ecd682 增加内存缓存队列长度,确保不会产生不在队列里的缓存对象 2023-10-02 15:20:19 +08:00
刘祥超
ae1454f9bb 优化热门缓存算法 2023-10-02 10:40:20 +08:00
刘祥超
3781b1920a 优化代码 2023-10-02 08:18:43 +08:00
刘祥超
125ad9c606 优化文件列表缓存时间 2023-10-01 20:30:07 +08:00
刘祥超
e516300dc7 修复一处测试用例 2023-10-01 19:48:35 +08:00
刘祥超
6c1d24c3e5 预留最大内存总是设置为系统内存的20% 2023-10-01 16:09:49 +08:00
刘祥超
6f230b30e0 调整WAF和其他配置的优先级顺序 2023-10-01 15:16:39 +08:00
刘祥超
8a0318b4f3 优化内存写入速度 2023-10-01 15:06:58 +08:00
刘祥超
d9fddcb001 优化内存缓存限制 2023-10-01 14:11:48 +08:00
刘祥超
9113c4c1b3 修复一处测试用例 2023-09-29 19:37:46 +08:00
刘祥超
f0762fe1b9 清理缓存时智能判断是否需要完整LFU 2023-09-29 14:52:08 +08:00
刘祥超
9054b8ec05 执行edge-node cache.badge命令时打印进度 2023-09-28 15:02:06 +08:00
刘祥超
5ad25e34c6 提升快速硬盘清理过期缓存速度 2023-09-28 10:56:33 +08:00
刘祥超
d4cca10301 修复一处无法编译的问题 2023-09-21 16:20:29 +08:00
刘祥超
8597884ec5 修复URL域名跳转设置可能不生效的问题 2023-09-20 08:21:22 +08:00
刘祥超
9f469f5b77 访客IP设置中支持多个请求报头/X-Real-IP也可以从变量中读取 2023-09-17 19:15:10 +08:00
刘祥超
5bc4f5b359 优化Ln请求 2023-09-17 18:17:34 +08:00
刘祥超
494ff5b5bb 智能调节清理缓存阈值 2023-09-17 12:05:06 +08:00
刘祥超
bac0060a74 edge-node cache.garbage命令执行时检查Key列表是否已加载完毕 2023-09-17 11:43:46 +08:00
刘祥超
062ca1cfcd 缓存内容报头时忽略X-Cache 2023-09-17 10:54:14 +08:00
刘祥超
8a4373e984 修复节点缓存磁盘容量设置不生效的问题 2023-09-16 09:36:04 +08:00
刘祥超
99670e46a5 增加edge-node cache.garbage命令用于清理垃圾缓存 2023-09-15 18:14:58 +08:00
刘祥超
64642c6680 优化单次清理LFU缓存数量逻辑 2023-09-15 14:46:31 +08:00
刘祥超
f7a7b50eda 优化缓存自动清理 2023-09-14 20:17:48 +08:00
刘祥超
f5265f1832 优化缓存LFU逻辑 2023-09-14 18:30:11 +08:00
刘祥超
2d6f7522fc 优化删除IP名单时操作 2023-09-13 17:17:05 +08:00
刘祥超
cfb15e764d 将版本号修改为1.2.9 2023-09-12 17:17:56 +08:00
刘祥超
406d8de999 本地数据库启动时在必要的情况下尝试恢复数据 2023-09-12 16:53:41 +08:00
刘祥超
2ca79953b9 优化一处测试用例 2023-09-12 16:51:15 +08:00
刘祥超
d067cd8437 IP名单同步时增加调试信息 2023-09-12 16:05:18 +08:00
刘祥超
e4937685bc 系统在进行健康检查时不进行指标统计 2023-09-12 10:45:44 +08:00
刘祥超
6ac2343aa7 更新依赖 2023-09-11 17:15:10 +08:00
刘祥超
1d80bc640d WebP转换增加宽度和高度限制 2023-09-11 16:05:59 +08:00
刘祥超
b19c431b89 优化代码 2023-09-07 15:10:05 +08:00
刘祥超
a1e1b5ef98 修复集群启用“允许使用节点IP访问”时无法访问IPv6的问题 2023-09-07 14:48:27 +08:00
刘祥超
e93275ac5c 重新实现套餐 2023-09-06 16:34:11 +08:00
刘祥超
a6059ab070 修复状未绑定域名中${status}和${statusMessage}变量值 2023-08-27 21:38:35 +08:00
刘祥超
5b0f94f317 优化过时缓存时长(从600秒改为1200秒) 2023-08-27 14:49:28 +08:00
刘祥超
0b4ac55aa6 源站返回50X时,也可以尝试使用过时缓存 2023-08-26 15:46:45 +08:00
刘祥超
9080c78b13 优化内置的消息提示:增加连接信息方便诊断 2023-08-25 15:43:12 +08:00
刘祥超
aa7d67e387 网站设置增加是否支持${serverAddr}选项/增强${serverAddr}安全性 2023-08-25 15:30:59 +08:00
刘祥超
405b3615fe 增加${serverAddr}变量 2023-08-25 15:04:18 +08:00
刘祥超
7534af09ed 优化编译脚本 2023-08-23 18:21:32 +08:00
刘祥超
cd3bf0cad4 优化编译脚本 2023-08-23 17:50:33 +08:00
刘祥超
41ebdfb7d2 优化响应报头 2023-08-23 16:33:19 +08:00
刘祥超
fa7d4963cb 反向代理增加是否重试50X选项,默认为启用 2023-08-20 15:50:31 +08:00
刘祥超
627d9721b3 优化反向代理错误处理代码 2023-08-20 11:45:39 +08:00
刘祥超
8c3cd53dc3 检查硬盘是否已满时同时检测缓存策略中定义的容量 2023-08-20 11:02:09 +08:00
刘祥超
5995be8489 修复访问日志无法记录自定义跳转状态码的问题 2023-08-20 10:10:23 +08:00
刘祥超
eabaa84252 修复磁盘空间可能为0的情形 2023-08-18 15:56:45 +08:00
刘祥超
40e137e69e 优化指标统计性能 2023-08-15 20:12:09 +08:00
刘祥超
6f51fe52f8 优化代码 2023-08-15 15:49:23 +08:00
刘祥超
dd4071e7dc 优化api_node.yaml生成 2023-08-15 10:33:01 +08:00
266 changed files with 23775 additions and 2939 deletions

View File

@@ -6,8 +6,11 @@ function build() {
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
DIST=$ROOT/"../dist/${NAME}"
MUSL_DIR="/usr/local/opt/musl-cross/bin"
GCC_X86_64_DIR="/usr/local/Cellar/x86_64-unknown-linux-gnu/10.3.0/bin"
GCC_ARM64_DIR="/usr/local/Cellar/aarch64-unknown-linux-gnu/10.3.0/bin"
SRCDIR=$(realpath "$ROOT/..")
# for macOS users: precompiled gcc can be downloaded from https://github.com/messense/homebrew-macos-cross-toolchains
GCC_X86_64_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
GCC_ARM64_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
OS=${1}
ARCH=${2}
@@ -57,7 +60,10 @@ function build() {
# we support TOA on linux only
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
then
mkdir "$DIST/edge-toa"
if [ ! -d "$DIST/edge-toa" ]
then
mkdir "$DIST/edge-toa"
fi
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
fi
@@ -65,6 +71,8 @@ function build() {
CC_PATH=""
CXX_PATH=""
CGO_LDFLAGS=""
CGO_CFLAGS=""
BUILD_TAG=$TAG
if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then
if [ "${ARCH}" == "amd64" ]; then
@@ -74,7 +82,7 @@ function build() {
CC_PATH="x86_64-unknown-linux-gnu-gcc"
CXX_PATH="x86_64-unknown-linux-gnu-g++"
if [ "$TAG" = "plus" ]; then
BUILD_TAG="plus,script"
BUILD_TAG="plus,script,packet"
fi
else
CC_PATH="x86_64-linux-musl-gcc"
@@ -92,7 +100,7 @@ function build() {
CC_PATH="aarch64-unknown-linux-gnu-gcc"
CXX_PATH="aarch64-unknown-linux-gnu-g++"
if [ "$TAG" = "plus" ]; then
BUILD_TAG="plus,script"
BUILD_TAG="plus,script,packet"
fi
else
CC_PATH="aarch64-linux-musl-gcc"
@@ -112,13 +120,31 @@ function build() {
CXX_PATH="mips64el-linux-musl-g++"
fi
fi
# libpcap
if [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
CGO_LDFLAGS="-L${SRCDIR}/libs/libpcap/${ARCH} -lpcap -L${SRCDIR}/libs/libbrotli/${ARCH} -lbrotlienc -lbrotlidec -lbrotlicommon"
CGO_CFLAGS="-I${SRCDIR}/libs/libpcap/src/libpcap -I${SRCDIR}/libs/libpcap/src/libpcap/pcap -I${SRCDIR}/libs/libbrotli/src/brotli/c/include"
fi
if [ ! -z $CC_PATH ]; then
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
env CC=$MUSL_DIR/$CC_PATH \
CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" \
GOARCH="${ARCH}" CGO_ENABLED=1 \
CGO_LDFLAGS="${CGO_LDFLAGS}" \
CGO_CFLAGS="${CGO_CFLAGS}" \
go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
else
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
BUILD_TAG="plus,script"
BUILD_TAG="plus,script,packet"
fi
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 CGO_LDFLAGS="${CGO_LDFLAGS}" CGO_CFLAGS="${CGO_CFLAGS}" go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
fi
if [ ! -f "${DIST}/bin/${NAME}" ]; then
echo "build failed!"
exit
fi
# delete hidden files

View File

@@ -6,4 +6,6 @@ if [ -z "$TAG" ]; then
TAG="community"
fi
go test -v ../... -tags=${TAG}
# reference: https://pkg.go.dev/cmd/go/internal/test
go clean -testcache
go test -timeout 10s -tags="${TAG}" -cover ../...

View File

@@ -16,12 +16,14 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3"
"net"
"net/http"
_ "net/http/pprof"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
@@ -29,8 +31,8 @@ func main() {
var app = apps.NewAppCmd().
Version(teaconst.Version).
Product(teaconst.ProductName).
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog|uninstall]").
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk]").
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|config|pprof|accesslog|uninstall]").
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk|cache.garbage]").
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
app.On("start:before", func() {
@@ -227,11 +229,18 @@ func main() {
})
app.On("gc", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
_, err := sock.Send(&gosock.Command{Code: "gc"})
reply, err := sock.Send(&gosock.Command{Code: "gc"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
fmt.Println("ok")
if reply == nil {
fmt.Println("ok")
} else {
var paramMap = maps.NewMap(reply.Params)
var pauseMS = paramMap.GetFloat64("pauseMS")
var costMS = paramMap.GetFloat64("costMS")
fmt.Printf("ok, cost: %.4fms, pause: %.4fms", costMS, pauseMS)
}
}
})
app.On("ip.drop", func() {
@@ -464,6 +473,93 @@ func main() {
fmt.Println("Usage: edge-node disk [speed]")
}
})
app.On("cache.garbage", func() {
fmt.Println("scanning ...")
var shouldDelete bool
for _, arg := range os.Args {
if strings.TrimLeft(arg, "-") == "delete" {
shouldDelete = true
}
}
var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
progressSock.OnCommand(func(cmd *gosock.Command) {
var params = maps.NewMap(cmd.Params)
if cmd.Code == "progress" {
fmt.Printf("%.2f%% %d\n", params.GetFloat64("progress")*100, params.GetInt("count"))
_ = cmd.ReplyOk()
}
})
go func() {
_ = progressSock.Listen()
}()
time.Sleep(1 * time.Second)
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{
Code: "cache.garbage",
Params: map[string]any{"delete": shouldDelete},
})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
return
}
var params = maps.NewMap(reply.Params)
if params.GetBool("isOk") {
var count = params.GetInt("count")
fmt.Println("found", count, "bad caches")
if count > 0 {
fmt.Println("======")
var sampleFiles = params.GetSlice("sampleFiles")
for _, file := range sampleFiles {
fmt.Println(types.String(file))
}
if count > len(sampleFiles) {
fmt.Println("... more files")
}
}
} else {
fmt.Println("[ERROR]" + params.GetString("error"))
}
})
app.On("config", func() {
var configString = os.Args[len(os.Args)-1]
if configString == "config" {
fmt.Println("Usage: edge-node config '\nrpc.endpoints: [\"...\"]\nnodeId: \"...\"\nsecret: \"...\"\n'")
return
}
var config = &configs.APIConfig{}
err := yaml.Unmarshal([]byte(configString), config)
if err != nil {
fmt.Println("[ERROR]decode config failed: " + err.Error())
return
}
err = config.Init()
if err != nil {
fmt.Println("[ERROR]validate config failed: " + err.Error())
return
}
// marshal again
configYAML, err := yaml.Marshal(config)
if err != nil {
fmt.Println("[ERROR]encode config failed: " + err.Error())
return
}
err = os.WriteFile(Tea.Root + "/configs/api_node.yaml", configYAML, 0666)
if err != nil {
fmt.Println("[ERROR]write config failed: " + err.Error())
return
}
fmt.Println("success")
})
app.Run(func() {
var node = nodes.NewNode()
node.Start()

46
go.mod
View File

@@ -13,46 +13,47 @@ require (
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
github.com/andybalholm/brotli v1.0.5
github.com/aws/aws-sdk-go v1.44.279
github.com/baidubce/bce-sdk-go v0.9.153
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
github.com/cespare/xxhash v1.1.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/fsnotify/fsnotify v1.6.0
github.com/go-redis/redis/v8 v8.11.5
github.com/golang/protobuf v1.5.3
github.com/google/gopacket v1.1.19
github.com/google/nftables v0.1.0
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508
github.com/klauspost/compress v1.16.5
github.com/mattn/go-sqlite3 v1.14.9
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4
github.com/klauspost/compress v1.17.2
github.com/mattn/go-sqlite3 v1.14.17
github.com/mdlayher/netlink v1.7.1
github.com/miekg/dns v1.1.43
github.com/mssola/useragent v1.0.0
github.com/pires/go-proxyproto v0.6.1
github.com/qiniu/go-sdk/v7 v7.16.0
github.com/quic-go/quic-go v0.36.1
github.com/quic-go/quic-go v0.39.2
github.com/shirou/gopsutil/v3 v3.22.2
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
golang.org/x/image v0.7.0
golang.org/x/net v0.12.0
golang.org/x/sys v0.10.0
google.golang.org/grpc v1.55.0
golang.org/x/image v0.13.0
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/webp v1.1.1 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
@@ -60,23 +61,22 @@ require (
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/tdewolff/minify/v2 v2.12.7 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.11.0 // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.11.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/protobuf v1.30.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
)

110
go.sum
View File

@@ -7,14 +7,14 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/baidubce/bce-sdk-go v0.9.153 h1:h5l2EXehe4C4/bdlAPBaULrbnEDgIu5HOYgniN7bjGM=
github.com/baidubce/bce-sdk-go v0.9.153/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
@@ -41,8 +41,6 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -53,10 +51,12 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g=
@@ -67,8 +67,8 @@ github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedV
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508 h1:fjKiHAyPQmdwuw1DQ2BI1JTbhUWAtI3Kr9wIZQBdRgQ=
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4 h1:eyymORsZg0tZ0niyolYF4nao4sdNUI+Ll40s96tKHBY=
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4/go.mod h1:AYyXDhbbD7q9N6rJff2jrE7pGupaiyvtv3YeyIAQLXk=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -79,8 +79,8 @@ github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTx
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@@ -93,8 +93,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
@@ -109,9 +109,9 @@ github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -126,12 +126,10 @@ github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYX
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.39.2 h1:hmwAf8zAHlvan0Y5PXxeeBFZEW17IW99sXLry8I2kjk=
github.com/quic-go/quic-go v0.39.2/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
@@ -161,49 +159,45 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -212,45 +206,39 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@@ -34,16 +34,14 @@ func CanIgnoreErr(err error) bool {
if err == nil {
return true
}
if err == ErrFileIsWriting ||
err == ErrEntityTooLarge ||
err == ErrWritingUnavailable ||
err == ErrWritingQueueFull ||
err == ErrServerIsBusy {
if errors.Is(err, ErrFileIsWriting) ||
errors.Is(err, ErrEntityTooLarge) ||
errors.Is(err, ErrWritingUnavailable) ||
errors.Is(err, ErrWritingQueueFull) ||
errors.Is(err, ErrServerIsBusy) {
return true
}
_, ok := err.(*CapacityError)
if ok {
return true
}
return false
var capacityErr *CapacityError
return errors.As(err, &capacityErr)
}

View File

@@ -1,16 +1,24 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
package caches_test
import (
"errors"
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestCanIgnoreErr(t *testing.T) {
a := assert.NewAssertion(t)
var a = assert.NewAssertion(t)
a.IsTrue(CanIgnoreErr(ErrFileIsWriting))
a.IsTrue(CanIgnoreErr(NewCapacityError("over capcity")))
a.IsFalse(CanIgnoreErr(ErrNotFound))
a.IsTrue(caches.CanIgnoreErr(caches.ErrFileIsWriting))
a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.ErrFileIsWriting)))
a.IsTrue(errors.Is(fmt.Errorf("error: %w", caches.ErrFileIsWriting), caches.ErrFileIsWriting))
a.IsTrue(errors.Is(caches.ErrFileIsWriting, caches.ErrFileIsWriting))
a.IsTrue(caches.CanIgnoreErr(caches.NewCapacityError("over capacity")))
a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.NewCapacityError("over capacity"))))
a.IsFalse(caches.CanIgnoreErr(caches.ErrNotFound))
a.IsFalse(caches.CanIgnoreErr(errors.New("test error")))
}

View File

@@ -3,7 +3,6 @@ package caches
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"strings"
"time"
)
type ItemType = int
@@ -16,7 +15,7 @@ const (
// 计算当前周
// 不要用YW因为需要计算两周是否临近
func currentWeek() int32 {
return int32(time.Now().Unix() / 86400)
return int32(fasttime.Now().Unix() / 86400)
}
type Item struct {
@@ -29,10 +28,7 @@ type Item struct {
MetaSize int64 `json:"metaSize"`
Host string `json:"host"` // 主机名
ServerId int64 `json:"serverId"` // 服务ID
Week1Hits int64 `json:"week1Hits"`
Week2Hits int64 `json:"week2Hits"`
Week int32 `json:"week"`
Week int32 `json:"week"`
}
func (this *Item) IsExpired() bool {
@@ -47,20 +43,6 @@ func (this *Item) Size() int64 {
return this.HeaderSize + this.BodySize
}
func (this *Item) IncreaseHit(week int32) {
if this.Week == week {
this.Week2Hits++
} else {
if week-this.Week == 1 {
this.Week1Hits = this.Week2Hits
} else {
this.Week1Hits = 0
}
this.Week2Hits = 1
this.Week = week
}
}
func (this *Item) RequestURI() string {
var schemeIndex = strings.Index(this.Key, "://")
if schemeIndex <= 0 {

View File

@@ -1,8 +1,10 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
@@ -11,27 +13,18 @@ import (
"time"
)
func TestItem_IncreaseHit(t *testing.T) {
var week = currentWeek()
var item = &Item{}
//item.Week = 2704
item.Week2Hits = 100
item.IncreaseHit(week)
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
item.IncreaseHit(week)
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
}
func TestItems_Memory(t *testing.T) {
var stat = &runtime.MemStats{}
runtime.ReadMemStats(stat)
var memory1 = stat.HeapInuse
var items = []*Item{}
for i := 0; i < 10_000_000; i++ {
items = append(items, &Item{
var items = []*caches.Item{}
var count = 100
if testutils.IsSingleTesting() {
count = 10_000_000
}
for i := 0; i < count; i++ {
items = append(items, &caches.Item{
Key: types.String(i),
})
}
@@ -41,18 +34,13 @@ func TestItems_Memory(t *testing.T) {
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
var weekItems = make(map[string]*Item, 10_000_000)
for _, item := range items {
weekItems[item.Key] = item
}
runtime.ReadMemStats(stat)
var memory3 = stat.HeapInuse
t.Log(memory2, memory3, (memory3-memory2)/1024/1024, "M")
time.Sleep(1 * time.Second)
t.Log(len(items), len(weekItems))
if testutils.IsSingleTesting() {
time.Sleep(1 * time.Second)
}
}
func TestItems_Memory2(t *testing.T) {
@@ -61,7 +49,12 @@ func TestItems_Memory2(t *testing.T) {
var memory1 = stat.HeapInuse
var items = map[int32]map[string]zero.Zero{}
for i := 0; i < 10_000_000; i++ {
var count = 100
if testutils.IsSingleTesting() {
count = 10_000_000
}
for i := 0; i < count; i++ {
var week = int32((time.Now().Unix() - int64(86400*rands.Int(0, 300))) / (86400 * 7))
m, ok := items[week]
if !ok {
@@ -76,7 +69,9 @@ func TestItems_Memory2(t *testing.T) {
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
time.Sleep(1 * time.Second)
if testutils.IsSingleTesting() {
time.Sleep(1 * time.Second)
}
for w, i := range items {
t.Log(w, len(i))
}
@@ -88,7 +83,7 @@ func TestItem_RequestURI(t *testing.T) {
"https://goedge.cn:8080/hello/world",
"https://goedge.cn/hello/world?v=1&t=123",
} {
var item = &Item{Key: u}
var item = &caches.Item{Key: u}
t.Log(u, "=>", item.RequestURI())
}
}

View File

@@ -4,13 +4,18 @@ package caches
import (
"database/sql"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/types"
"os"
"strings"
"sync"
"time"
)
@@ -24,7 +29,7 @@ type FileList struct {
onAdd func(item *Item)
onRemove func(item *Item)
memoryCache *ttlcache.Cache
memoryCache *ttlcache.Cache[zero.Zero]
// 老数据库地址
oldDir string
@@ -33,7 +38,7 @@ type FileList struct {
func NewFileList(dir string) ListInterface {
return &FileList{
dir: dir,
memoryCache: ttlcache.NewCache(),
memoryCache: ttlcache.NewCache[zero.Zero](),
}
}
@@ -59,19 +64,37 @@ func (this *FileList) Init() error {
}
remotelogs.Println("CACHE", "loading database from '"+dir+"' ...")
var wg = &sync.WaitGroup{}
var locker = sync.Mutex{}
var lastErr error
for i := 0; i < CountFileDB; i++ {
var db = NewFileListDB()
err = db.Open(dir + "/db-" + types.String(i) + ".db")
if err != nil {
return err
}
wg.Add(1)
go func(i int) {
defer wg.Done()
err = db.Init()
if err != nil {
return err
}
var db = NewFileListDB()
dbErr := db.Open(dir + "/db-" + types.String(i) + ".db")
if dbErr != nil {
lastErr = dbErr
return
}
this.dbList[i] = db
dbErr = db.Init()
if dbErr != nil {
lastErr = dbErr
return
}
locker.Lock()
this.dbList[i] = db
locker.Unlock()
}(i)
}
wg.Wait()
if lastErr != nil {
return lastErr
}
// 升级老版本数据库
@@ -99,9 +122,7 @@ func (this *FileList) Add(hash string, item *Item) error {
return err
}
// 这里不增加点击量,以减少对数据库的操作次数
this.memoryCache.Write(hash, 1, item.ExpiredAt)
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(item.ExpiredAt))
if this.onAdd != nil {
this.onAdd(item)
@@ -134,15 +155,27 @@ func (this *FileList) Exist(hash string) (bool, error) {
var expiredAt int64
err := row.Scan(&expiredAt)
if err != nil {
if err == sql.ErrNoRows {
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
return false, err
}
this.memoryCache.Write(hash, 1, expiredAt)
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt))
return true, nil
}
func (this *FileList) ExistQuick(hash string) (isReady bool, found bool) {
var db = this.GetDB(hash)
if !db.IsReady() || !db.HashMapIsLoaded() {
return
}
isReady = true
found = db.hashMap.Exist(hash)
return
}
// CleanPrefix 清理某个前缀的缓存数据
func (this *FileList) CleanPrefix(prefix string) error {
if len(prefix) == 0 {
@@ -204,7 +237,7 @@ func (this *FileList) CleanMatchPrefix(prefix string) error {
}
func (this *FileList) Remove(hash string) error {
_, err := this.remove(hash)
_, err := this.remove(hash, false)
return err
}
@@ -223,11 +256,16 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
if err != nil {
return 0, nil
}
if len(hashStrings) == 0 {
continue
}
countFound += len(hashStrings)
// 不在 rows.Next() 循环中操作是为了避免死锁
for _, hash := range hashStrings {
err = this.Remove(hash)
_, err = this.remove(hash, true)
if err != nil {
return 0, err
}
@@ -237,6 +275,11 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
return 0, err
}
}
_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
if err != nil {
return 0, err
}
}
return countFound, nil
@@ -254,24 +297,27 @@ func (this *FileList) PurgeLFU(count int, callback func(hash string) error) erro
return err
}
if len(hashStrings) == 0 {
continue
}
// 不在 rows.Next() 循环中操作是为了避免死锁
for _, hash := range hashStrings {
notFound, err := this.remove(hash)
_, err = this.remove(hash, true)
if err != nil {
return err
}
if notFound {
err = db.DeleteHitAsync(hash)
if err != nil {
return db.WrapError(err)
}
}
err = callback(hash)
if err != nil {
return err
}
}
_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
if err != nil {
return err
}
}
return nil
}
@@ -372,7 +418,16 @@ func (this *FileList) GetDB(hash string) *FileListDB {
return this.dbList[fnv.HashString(hash)%CountFileDB]
}
func (this *FileList) remove(hash string) (notFound bool, err error) {
func (this *FileList) HashMapIsLoaded() bool {
for _, db := range this.dbList {
if !db.HashMapIsLoaded() {
return false
}
}
return true
}
func (this *FileList) remove(hash string, isDeleted bool) (notFound bool, err error) {
var db = this.GetDB(hash)
if !db.IsReady() {
@@ -388,14 +443,11 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
// 从缓存中删除
this.memoryCache.Delete(hash)
err = db.DeleteSync(hash)
if err != nil {
return false, db.WrapError(err)
}
err = db.DeleteHitAsync(hash)
if err != nil {
return false, db.WrapError(err)
if !isDeleted {
err = db.DeleteSync(hash)
if err != nil {
return false, db.WrapError(err)
}
}
if this.onRemove != nil {
@@ -428,7 +480,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
remotelogs.Println("CACHE", "upgrading local database finished")
}()
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
@@ -493,9 +545,6 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
MetaSize: metaSize,
Host: host,
ServerId: serverId,
Week1Hits: 0,
Week2Hits: 0,
Week: 0,
})
if err != nil {
if brokenOnError {
@@ -515,3 +564,11 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
return nil
}
func (this *FileList) maxExpiresAtForMemoryCache(expiresAt int64) int64 {
var maxTimestamp = fasttime.Now().Unix() + 3600
if expiresAt > maxTimestamp {
return maxTimestamp
}
return expiresAt
}

View File

@@ -6,18 +6,15 @@ import (
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"net"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
@@ -29,15 +26,13 @@ type FileListDB struct {
readDB *dbs.DB
writeDB *dbs.DB
writeBatch *dbs.Batch
hashMap *FileListHashMap
itemsTableName string
hitsTableName string
isClosed bool
isReady bool
isClosed bool // 是否已关闭
isReady bool // 是否已完成初始化
hashMapIsLoaded bool // Hash是否已加载
// cacheItems
existsByHashStmt *dbs.Stmt // 根据hash检查是否存在
@@ -52,16 +47,10 @@ type FileListDB struct {
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
deleteByHashSQL string
statStmt *dbs.Stmt // 统计
purgeStmt *dbs.Stmt // 清理
deleteAllStmt *dbs.Stmt // 删除所有数据
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
updateAccessWeekSQL string // 修改访问日期
// hits
insertHitSQL string // 写入数据
increaseHitSQL string // 增加点击量
deleteHitByHashSQL string // 根据hash删除数据
statStmt *dbs.Stmt // 统计
purgeStmt *dbs.Stmt // 清理
deleteAllStmt *dbs.Stmt // 删除所有数据
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
}
func NewFileListDB() *FileListDB {
@@ -74,15 +63,15 @@ func (this *FileListDB) Open(dbPath string) error {
this.dbPath = dbPath
// 动态调整Cache值
var cacheSize = 32000
var cacheSize = 512
var memoryGB = utils.SystemMemoryGB()
if memoryGB >= 8 {
cacheSize += 32000 * memoryGB / 8
if memoryGB >= 1 {
cacheSize = 256 * memoryGB
}
// write db
// 这里不能加 EXCLUSIVE 锁,不然异步事务可能会失败
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
if err != nil {
return fmt.Errorf("open write database failed: %w", err)
}
@@ -109,17 +98,7 @@ func (this *FileListDB) Open(dbPath string) error {
}
}
this.writeBatch = dbs.NewBatch(writeDB, 4)
this.writeBatch.OnFail(func(err error) {
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
})
goman.New(func() {
this.writeBatch.Exec()
})
if teaconst.EnableDBStat {
this.writeBatch.EnableStat(true)
this.writeDB.EnableStat(true)
}
@@ -142,7 +121,6 @@ func (this *FileListDB) Open(dbPath string) error {
func (this *FileListDB) Init() error {
this.itemsTableName = "cacheItems"
this.hitsTableName = "hits"
// 创建
var err = this.initTables(1)
@@ -156,7 +134,7 @@ func (this *FileListDB) Init() error {
return err
}
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt", "accessWeek") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
if err != nil {
return err
@@ -167,7 +145,7 @@ func (this *FileListDB) Init() error {
return err
}
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>? ORDER BY id ASC LIMIT 2000`)
if err != nil {
return err
}
@@ -193,42 +171,15 @@ func (this *FileListDB) Init() error {
return err
}
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "accessWeek" ASC, "id" ASC LIMIT ?`)
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
if err != nil {
return err
}
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
this.increaseHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?) ON CONFLICT("hash") DO UPDATE SET "week1Hits"=IIF("week"=?, "week1Hits", "week2Hits"), "week2Hits"=IIF("week"=?, "week2Hits"+1, 1), "week"=?`
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
this.isReady = true
// 加载HashMap
go func() {
err := this.hashMap.Load(this)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
// 自动修复错误
// TODO 将来希望能尽可能恢复以往数据库中的内容
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
_ = this.Close()
this.deleteDB()
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
err = this.Open(this.dbPath)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
} else {
_ = this.Init()
}
}
}
}()
go this.loadHashMap()
return nil
}
@@ -255,7 +206,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
item.StaleAt = item.ExpiredAt
}
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix())
if err != nil {
return this.WrapError(err)
}
@@ -351,14 +302,7 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
}
func (this *FileListDB) IncreaseHitAsync(hash string) error {
var week = timeutil.Format("YW")
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
return nil
}
func (this *FileListDB) DeleteHitAsync(hash string) error {
this.writeBatch.Add(this.deleteHitByHashSQL, hash)
// do nothing
return nil
}
@@ -367,10 +311,9 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
return nil
}
var count = int64(10000)
var staleLife = 600 // TODO 需要可以设置
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
for {
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+DefaultStaleCacheSeconds, unixTime, prefix)
if err != nil {
return this.WrapError(err)
}
@@ -414,15 +357,14 @@ func (this *FileListDB) CleanMatchKey(key string) error {
queryKey = strings.Replace(queryKey, "*", "%", 1)
// TODO 检查大批量数据下的操作性能
var staleLife = 600 // TODO 需要可以设置
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey)
if err != nil {
return err
}
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey+SuffixAll+"%")
if err != nil {
return err
}
@@ -456,10 +398,9 @@ func (this *FileListDB) CleanMatchPrefix(prefix string) error {
queryPrefix += "%"
// TODO 检查大批量数据下的操作性能
var staleLife = 600 // TODO 需要可以设置
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryPrefix)
return err
}
@@ -543,6 +484,10 @@ func (this *FileListDB) WrapError(err error) error {
return fmt.Errorf("%w (file: %s)", err, this.dbPath)
}
func (this *FileListDB) HashMapIsLoaded() bool {
return this.hashMapIsLoaded
}
// 初始化
func (this *FileListDB) initTables(times int) error {
{
@@ -561,8 +506,7 @@ func (this *FileListDB) initTables(times int) error {
"staleAt" integer DEFAULT 0,
"createdAt" integer DEFAULT 0,
"host" varchar(128),
"serverId" integer,
"accessWeek" varchar(6)
"serverId" integer
);
DROP INDEX IF EXISTS "createdAt";
@@ -578,8 +522,6 @@ CREATE INDEX IF NOT EXISTS "hash"
ON "` + this.itemsTableName + `" (
"hash" ASC
);
ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
`)
if err != nil {
@@ -603,32 +545,9 @@ ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
}
}
// 删除hits表
{
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32),
"week1Hits" integer DEFAULT 0,
"week2Hits" integer DEFAULT 0,
"week" varchar(6)
);
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
ON "` + this.hitsTableName + `" (
"hash" ASC
);
`)
if err != nil {
// 尝试删除重建
if times < 3 {
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
if dropErr == nil {
return this.initTables(times + 1)
}
return this.WrapError(err)
}
return this.WrapError(err)
}
_, _ = this.writeDB.Exec(`DROP TABLE "hits"`)
}
return nil
@@ -678,3 +597,30 @@ func (this *FileListDB) deleteDB() {
_ = os.Remove(this.dbPath + "-shm")
_ = os.Remove(this.dbPath + "-wal")
}
// 加载Hash列表
func (this *FileListDB) loadHashMap() {
this.hashMapIsLoaded = false
err := this.hashMap.Load(this)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
// 自动修复错误
// TODO 将来希望能尽可能恢复以往数据库中的内容
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
_ = this.Close()
this.deleteDB()
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
err = this.Open(this.dbPath)
if err != nil {
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
} else {
_ = this.Init()
}
}
return
}
this.hashMapIsLoaded = true
}

View File

@@ -4,13 +4,20 @@ package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"runtime"
"runtime/debug"
"testing"
"time"
)
func TestFileListDB_ListLFUItems(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var db = caches.NewFileListDB()
defer func() {
@@ -34,33 +41,11 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
t.Log("[", len(hashList), "]", hashList)
}
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
var db = caches.NewFileListDB()
defer func() {
_ = db.Close()
}()
err := db.Open(Tea.Root + "/data/cache-db-large.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
if err != nil {
t.Fatal(err)
}
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
if err != nil {
t.Fatal(err)
}
// wait transaction
time.Sleep(1 * time.Second)
}
func TestFileListDB_CleanMatchKey(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var db = caches.NewFileListDB()
defer func() {
@@ -89,6 +74,10 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
}
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var db = caches.NewFileListDB()
defer func() {
@@ -115,3 +104,67 @@ func TestFileListDB_CleanMatchPrefix(t *testing.T) {
t.Fatal(err)
}
}
func TestFileListDB_Memory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var db = caches.NewFileListDB()
defer func() {
_ = db.Close()
}()
err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
if err != nil {
t.Fatal(err)
}
err = db.Init()
if err != nil {
t.Fatal(err)
}
t.Log(db.Total())
// load hashes
var maxId int64
var hashList []string
var before = time.Now()
for i := 0; i < 1_000; i++ {
hashList, maxId, err = db.ListHashes(maxId)
if err != nil {
t.Fatal(err)
}
if len(hashList) == 0 {
t.Log("hashes loaded", time.Since(before).Seconds()*1000, "ms")
break
}
if i%100 == 0 {
t.Log(i)
}
}
runtime.GC()
debug.FreeOSMemory()
//time.Sleep(600 * time.Second)
for i := 0; i < 1_000; i++ {
_, err = db.ListLFUItems(5000)
if err != nil {
t.Fatal(err)
}
if i%100 == 0 {
t.Log(i)
}
}
t.Log("loaded")
runtime.GC()
debug.FreeOSMemory()
time.Sleep(600 * time.Second)
}

View File

@@ -9,18 +9,36 @@ import (
"sync"
)
const HashMapSharding = 31
var bigIntPool = sync.Pool{
New: func() any {
return big.NewInt(0)
},
}
// FileListHashMap 文件Hash列表
type FileListHashMap struct {
m map[uint64]zero.Zero
m []map[uint64]zero.Zero
lockers []*sync.RWMutex
locker sync.RWMutex
isAvailable bool
isReady bool
}
func NewFileListHashMap() *FileListHashMap {
var m = make([]map[uint64]zero.Zero, HashMapSharding)
var lockers = make([]*sync.RWMutex, HashMapSharding)
for i := 0; i < HashMapSharding; i++ {
m[i] = map[uint64]zero.Zero{}
lockers[i] = &sync.RWMutex{}
}
return &FileListHashMap{
m: map[uint64]zero.Zero{},
m: m,
lockers: lockers,
isAvailable: false,
isReady: false,
}
@@ -35,6 +53,7 @@ func (this *FileListHashMap) Load(db *FileListDB) error {
this.isAvailable = true
var lastId int64
var maxLoops = 50_000
for {
hashList, maxId, err := db.ListHashes(lastId)
if err != nil {
@@ -45,6 +64,11 @@ func (this *FileListHashMap) Load(db *FileListDB) error {
}
this.AddHashes(hashList)
lastId = maxId
maxLoops--
if maxLoops <= 0 {
break
}
}
this.isReady = true
@@ -56,9 +80,11 @@ func (this *FileListHashMap) Add(hash string) {
return
}
this.locker.Lock()
this.m[this.bigInt(hash)] = zero.New()
this.locker.Unlock()
hashInt, index := this.bigInt(hash)
this.lockers[index].Lock()
this.m[index][hashInt] = zero.New()
this.lockers[index].Unlock()
}
func (this *FileListHashMap) AddHashes(hashes []string) {
@@ -66,11 +92,12 @@ func (this *FileListHashMap) AddHashes(hashes []string) {
return
}
this.locker.Lock()
for _, hash := range hashes {
this.m[this.bigInt(hash)] = zero.New()
hashInt, index := this.bigInt(hash)
this.lockers[index].Lock()
this.m[index][hashInt] = zero.New()
this.lockers[index].Unlock()
}
this.locker.Unlock()
}
func (this *FileListHashMap) Delete(hash string) {
@@ -78,9 +105,10 @@ func (this *FileListHashMap) Delete(hash string) {
return
}
this.locker.Lock()
delete(this.m, this.bigInt(hash))
this.locker.Unlock()
hashInt, index := this.bigInt(hash)
this.lockers[index].Lock()
delete(this.m[index], hashInt)
this.lockers[index].Unlock()
}
func (this *FileListHashMap) Exist(hash string) bool {
@@ -91,16 +119,28 @@ func (this *FileListHashMap) Exist(hash string) bool {
// 只有完全Ready时才能判断是否为false
return true
}
this.locker.RLock()
_, ok := this.m[this.bigInt(hash)]
this.locker.RUnlock()
hashInt, index := this.bigInt(hash)
this.lockers[index].RLock()
_, ok := this.m[index][hashInt]
this.lockers[index].RUnlock()
return ok
}
func (this *FileListHashMap) Clean() {
this.locker.Lock()
this.m = map[uint64]zero.Zero{}
this.locker.Unlock()
for i := 0; i < HashMapSharding; i++ {
this.lockers[i].Lock()
}
// 这里不能简单清空 this.m ,避免导致别的数据无法写入 map 而产生 panic
for i := 0; i < HashMapSharding; i++ {
this.m[i] = map[uint64]zero.Zero{}
}
for i := HashMapSharding - 1; i >= 0; i-- {
this.lockers[i].Unlock()
}
}
func (this *FileListHashMap) IsReady() bool {
@@ -108,13 +148,36 @@ func (this *FileListHashMap) IsReady() bool {
}
func (this *FileListHashMap) Len() int {
this.locker.Lock()
defer this.locker.Unlock()
return len(this.m)
for i := 0; i < HashMapSharding; i++ {
this.lockers[i].Lock()
}
var count = 0
for _, shard := range this.m {
count += len(shard)
}
for i := HashMapSharding - 1; i >= 0; i-- {
this.lockers[i].Unlock()
}
return count
}
func (this *FileListHashMap) bigInt(hash string) uint64 {
var bigInt = big.NewInt(0)
bigInt.SetString(hash, 16)
return bigInt.Uint64()
func (this *FileListHashMap) SetIsAvailable(isAvailable bool) {
this.isAvailable = isAvailable
}
func (this *FileListHashMap) SetIsReady(isReady bool) {
this.isReady = isReady
}
func (this *FileListHashMap) bigInt(hash string) (hashInt uint64, index int) {
var bigInt = bigIntPool.Get().(*big.Int)
bigInt.SetString(hash, 16)
hashInt = bigInt.Uint64()
bigIntPool.Put(bigInt)
index = int(hashInt % HashMapSharding)
return
}

View File

@@ -4,8 +4,11 @@ package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"math/big"
@@ -20,15 +23,19 @@ func TestFileListHashMap_Memory(t *testing.T) {
runtime.ReadMemStats(stat1)
var m = caches.NewFileListHashMap()
m.SetIsAvailable(true)
for i := 0; i < 1_000_000; i++ {
m.Add(stringutil.Md5(types.String(i)))
}
t.Log("added:", m.Len(), "hashes")
var stat2 = &runtime.MemStats{}
runtime.ReadMemStats(stat2)
t.Log("ready", (stat2.Alloc-stat1.Alloc)/1024/1024, "M")
t.Log("remains:", m.Len(), "hashes")
}
func TestFileListHashMap_Memory2(t *testing.T) {
@@ -48,16 +55,42 @@ func TestFileListHashMap_Memory2(t *testing.T) {
}
func TestFileListHashMap_BigInt(t *testing.T) {
var bigInt = big.NewInt(0)
for _, s := range []string{"1", "2", "3", "123", "123456"} {
var hash = stringutil.Md5(s)
var bigInt = big.NewInt(0)
var bigInt1 = big.NewInt(0)
bigInt1.SetString(hash, 16)
bigInt.SetString(hash, 16)
t.Log(s, "=>", bigInt.Uint64(), "hash:", hash, "format:", strconv.FormatUint(bigInt.Uint64(), 16))
t.Log(s, "=>", bigInt1.Uint64(), "hash:", hash, "format:", strconv.FormatUint(bigInt1.Uint64(), 16), strconv.FormatUint(bigInt.Uint64(), 16))
if strconv.FormatUint(bigInt1.Uint64(), 16) != strconv.FormatUint(bigInt.Uint64(), 16) {
t.Fatal("not equal")
}
}
for i := 0; i < 1_000_000; i++ {
var hash = stringutil.Md5(types.String(i))
var bigInt1 = big.NewInt(0)
bigInt1.SetString(hash, 16)
bigInt.SetString(hash, 16)
if bigInt1.Uint64() != bigInt.Uint64() {
t.Fatal(i, "not equal")
}
}
}
func TestFileListHashMap_Load(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
@@ -85,6 +118,25 @@ func TestFileListHashMap_Load(t *testing.T) {
}
}
func TestFileListHashMap_Delete(t *testing.T) {
var a = assert.NewAssertion(t)
var m = caches.NewFileListHashMap()
m.SetIsReady(true)
m.SetIsAvailable(true)
m.Add("a")
a.IsTrue(m.Len() == 1)
m.Delete("a")
a.IsTrue(m.Len() == 0)
}
func TestFileListHashMap_Clean(t *testing.T) {
var m = caches.NewFileListHashMap()
m.SetIsAvailable(true)
m.Clean()
m.Add("a")
}
func Benchmark_BigInt(b *testing.B) {
var hash = stringutil.Md5("123456")
b.ResetTimer()
@@ -95,3 +147,23 @@ func Benchmark_BigInt(b *testing.B) {
_ = bigInt.Uint64()
}
}
func BenchmarkFileListHashMap_Exist(b *testing.B) {
var m = caches.NewFileListHashMap()
m.SetIsAvailable(true)
m.SetIsReady(true)
for i := 0; i < 1_000_000; i++ {
m.Add(types.String(i))
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
m.Add(types.String(rands.Int64()))
_ = m.Exist(types.String(rands.Int64()))
}
})
}

View File

@@ -17,6 +17,10 @@ import (
)
func TestFileList_Init(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
@@ -34,6 +38,10 @@ func TestFileList_Init(t *testing.T) {
}
func TestFileList_Add(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
@@ -107,6 +115,10 @@ func TestFileList_Add_Many(t *testing.T) {
}
func TestFileList_Exist(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
_ = list.Close()
@@ -143,6 +155,10 @@ func TestFileList_Exist(t *testing.T) {
}
func TestFileList_Exist_Many_DB(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
// 测试在多个数据库下的性能
var listSlice = []caches.ListInterface{}
for i := 1; i <= 10; i++ {
@@ -202,6 +218,10 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
}
func TestFileList_CleanPrefix(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
@@ -222,6 +242,10 @@ func TestFileList_CleanPrefix(t *testing.T) {
}
func TestFileList_Remove(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
defer func() {
_ = list.Close()
@@ -246,6 +270,10 @@ func TestFileList_Remove(t *testing.T) {
}
func TestFileList_Purge(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
@@ -270,6 +298,10 @@ func TestFileList_Purge(t *testing.T) {
}
func TestFileList_PurgeLFU(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
@@ -281,11 +313,6 @@ func TestFileList_PurgeLFU(t *testing.T) {
t.Fatal(err)
}
err = list.IncreaseHit(stringutil.Md5("123456"))
if err != nil {
t.Fatal(err)
}
var count = 0
err = list.PurgeLFU(caches.CountFileDB*2, func(hash string) error {
t.Log(hash)
@@ -299,6 +326,10 @@ func TestFileList_PurgeLFU(t *testing.T) {
}
func TestFileList_Stat(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
@@ -318,6 +349,10 @@ func TestFileList_Stat(t *testing.T) {
}
func TestFileList_Count(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data")
defer func() {
@@ -338,6 +373,10 @@ func TestFileList_Count(t *testing.T) {
}
func TestFileList_CleanAll(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data")
defer func() {
@@ -356,42 +395,11 @@ func TestFileList_CleanAll(t *testing.T) {
t.Log(list.Count())
}
func TestFileList_IncreaseHit(t *testing.T) {
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {
_ = list.Close()
}()
err := list.Init()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = list.Close()
}()
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
var count = 1_000_000
if !testutils.IsSingleTesting() {
count = 10
}
for i := 0; i < count; i++ {
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
}
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestFileList_UpgradeV3(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
defer func() {
@@ -416,6 +424,10 @@ func TestFileList_UpgradeV3(t *testing.T) {
}
func BenchmarkFileList_Exist(b *testing.B) {
if !testutils.IsSingleTesting() {
return
}
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
defer func() {

View File

@@ -2,7 +2,6 @@ package caches
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/logs"
"net"
"net/url"
@@ -19,9 +18,6 @@ type MemoryList struct {
itemMaps map[string]map[string]*Item // prefix => { hash => item }
weekItemMaps map[int32]map[string]zero.Zero // week => { hash => Zero }
minWeek int32
prefixes []string
locker sync.RWMutex
onAdd func(item *Item)
@@ -32,9 +28,7 @@ type MemoryList struct {
func NewMemoryList() ListInterface {
return &MemoryList{
itemMaps: map[string]map[string]*Item{},
weekItemMaps: map[int32]map[string]zero.Zero{},
minWeek: currentWeek(),
itemMaps: map[string]map[string]*Item{},
}
}
@@ -56,7 +50,6 @@ func (this *MemoryList) Reset() error {
for key := range this.itemMaps {
this.itemMaps[key] = map[string]*Item{}
}
this.weekItemMaps = map[int32]map[string]zero.Zero{}
this.locker.Unlock()
atomic.StoreInt64(&this.count, 0)
@@ -65,10 +58,6 @@ func (this *MemoryList) Reset() error {
}
func (this *MemoryList) Add(hash string, item *Item) error {
if item.Week == 0 {
item.Week = currentWeek()
}
this.locker.Lock()
prefix := this.prefix(hash)
@@ -81,14 +70,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
// 先删除,为了可以正确触发统计
oldItem, ok := itemMap[hash]
if ok {
// 从week map中删除
if oldItem.Week > 0 {
wm, ok := this.weekItemMaps[oldItem.Week]
if ok {
delete(wm, hash)
}
}
// 回调
if this.onRemove != nil {
this.onRemove(oldItem)
@@ -104,14 +85,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
itemMap[hash] = item
// week map
wm, ok := this.weekItemMaps[item.Week]
if ok {
wm[hash] = zero.New()
} else {
this.weekItemMaps[item.Week] = map[string]zero.Zero{hash: zero.New()}
}
this.locker.Unlock()
return nil
}
@@ -242,14 +215,6 @@ func (this *MemoryList) Remove(hash string) error {
atomic.AddInt64(&this.count, -1)
delete(itemMap, hash)
// week map
if item.Week > 0 {
wm, ok := this.weekItemMaps[item.Week]
if ok {
delete(wm, hash)
}
}
}
this.locker.Unlock()
@@ -261,12 +226,12 @@ func (this *MemoryList) Remove(hash string) error {
// callback 每次发现过期key的调用
func (this *MemoryList) Purge(count int, callback func(hash string) error) (int, error) {
this.locker.Lock()
deletedHashList := []string{}
var deletedHashList = []string{}
if this.purgeIndex >= len(this.prefixes) {
this.purgeIndex = 0
}
prefix := this.prefixes[this.purgeIndex]
var prefix = this.prefixes[this.purgeIndex]
this.purgeIndex++
@@ -290,14 +255,6 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) (int,
delete(itemMap, hash)
deletedHashList = append(deletedHashList, hash)
// week map
if item.Week > 0 {
wm, ok := this.weekItemMaps[item.Week]
if ok {
delete(wm, hash)
}
}
countFound++
}
@@ -322,63 +279,48 @@ func (this *MemoryList) PurgeLFU(count int, callback func(hash string) error) er
return nil
}
var week = currentWeek()
if this.minWeek > week {
this.minWeek = week
}
var deletedHashList = []string{}
var week = currentWeek()
var round = 0
this.locker.Lock()
Loop:
for w := this.minWeek; w <= week; w++ {
this.minWeek = w
for {
var found = false
round++
for _, itemMap := range this.itemMaps {
for hash, item := range itemMap {
found = true
this.locker.Lock()
wm, ok := this.weekItemMaps[w]
if ok {
var wc = len(wm)
if wc == 0 {
delete(this.weekItemMaps, w)
} else {
if wc <= count {
delete(this.weekItemMaps, w)
if week-item.Week <= 1 /** 最近有在使用 **/ && round <= 3 /** 查找轮数过多还不满足数量要求的就不再限制 **/ {
continue
}
// TODO 未来支持按照点击量排序
for hash := range wm {
count--
if count < 0 {
this.locker.Unlock()
break Loop
}
delete(wm, hash)
itemMap, ok := this.itemMaps[this.prefix(hash)]
if !ok {
continue
}
item, ok := itemMap[hash]
if !ok {
continue
}
if this.onRemove != nil {
this.onRemove(item)
}
atomic.AddInt64(&this.count, -1)
delete(itemMap, hash)
deletedHashList = append(deletedHashList, hash)
if this.onRemove != nil {
this.onRemove(item)
}
atomic.AddInt64(&this.count, -1)
delete(itemMap, hash)
deletedHashList = append(deletedHashList, hash)
count--
if count <= 0 {
break Loop
}
break
}
} else {
delete(this.weekItemMaps, w)
}
this.locker.Unlock()
if !found {
break
}
}
this.locker.Unlock()
// 执行外部操作
for _, hash := range deletedHashList {
if callback != nil {
@@ -451,30 +393,26 @@ func (this *MemoryList) IncreaseHit(hash string) error {
item, ok := itemMap[hash]
if ok {
var week = currentWeek()
// 交换位置
if item.Week > 0 && item.Week != week {
wm, ok := this.weekItemMaps[item.Week]
if ok {
delete(wm, hash)
}
wm, ok = this.weekItemMaps[week]
if ok {
wm[hash] = zero.New()
} else {
this.weekItemMaps[week] = map[string]zero.Zero{hash: zero.New()}
}
}
item.IncreaseHit(week)
item.Week = currentWeek()
}
this.locker.Unlock()
return nil
}
func (this *MemoryList) print(t *testing.T) {
func (this *MemoryList) Prefixes() []string {
return this.prefixes
}
func (this *MemoryList) ItemMaps() map[string]map[string]*Item {
return this.itemMaps
}
func (this *MemoryList) PurgeIndex() int {
return this.purgeIndex
}
func (this *MemoryList) Print(t *testing.T) {
this.locker.Lock()
for _, itemMap := range this.itemMaps {
if len(itemMap) > 0 {

View File

@@ -1,79 +1,81 @@
package caches
package caches_test
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/cespare/xxhash"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"math/rand"
"sort"
"strconv"
"testing"
"time"
)
func TestMemoryList_Add(t *testing.T) {
list := NewMemoryList().(*MemoryList)
list := caches.NewMemoryList().(*caches.MemoryList)
_ = list.Init()
_ = list.Add("a", &Item{
_ = list.Add("a", &caches.Item{
Key: "a1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("b", &Item{
_ = list.Add("b", &caches.Item{
Key: "b1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("123456", &Item{
_ = list.Add("123456", &caches.Item{
Key: "c1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
t.Log(list.prefixes)
logs.PrintAsJSON(list.itemMaps, t)
logs.PrintAsJSON(list.weekItemMaps, t)
t.Log(list.Prefixes())
logs.PrintAsJSON(list.ItemMaps(), t)
t.Log(list.Count())
}
func TestMemoryList_Remove(t *testing.T) {
list := NewMemoryList().(*MemoryList)
list := caches.NewMemoryList().(*caches.MemoryList)
_ = list.Init()
_ = list.Add("a", &Item{
_ = list.Add("a", &caches.Item{
Key: "a1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("b", &Item{
_ = list.Add("b", &caches.Item{
Key: "b1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Remove("b")
list.print(t)
logs.PrintAsJSON(list.weekItemMaps, t)
list.Print(t)
t.Log(list.Count())
}
func TestMemoryList_Purge(t *testing.T) {
list := NewMemoryList().(*MemoryList)
list := caches.NewMemoryList().(*caches.MemoryList)
_ = list.Init()
_ = list.Add("a", &Item{
_ = list.Add("a", &caches.Item{
Key: "a1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("b", &Item{
_ = list.Add("b", &caches.Item{
Key: "b1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("c", &Item{
_ = list.Add("c", &caches.Item{
Key: "c1",
ExpiredAt: time.Now().Unix() - 3600,
HeaderSize: 1024,
})
_ = list.Add("d", &Item{
_ = list.Add("d", &caches.Item{
Key: "d1",
ExpiredAt: time.Now().Unix() - 2,
HeaderSize: 1024,
@@ -82,26 +84,30 @@ func TestMemoryList_Purge(t *testing.T) {
t.Log("delete:", hash)
return nil
})
list.print(t)
logs.PrintAsJSON(list.weekItemMaps, t)
list.Print(t)
for i := 0; i < 1000; i++ {
_, _ = list.Purge(100, func(hash string) error {
t.Log("delete:", hash)
return nil
})
t.Log(list.purgeIndex)
t.Log(list.PurgeIndex())
}
t.Log(list.Count())
}
func TestMemoryList_Purge_Large_List(t *testing.T) {
list := NewMemoryList().(*MemoryList)
var list = caches.NewMemoryList().(*caches.MemoryList)
_ = list.Init()
for i := 0; i < 1_000_000; i++ {
_ = list.Add("a"+strconv.Itoa(i), &Item{
var count = 100
if testutils.IsSingleTesting() {
count = 1_000_000
}
for i := 0; i < count; i++ {
_ = list.Add("a"+strconv.Itoa(i), &caches.Item{
Key: "a" + strconv.Itoa(i),
ExpiredAt: time.Now().Unix() + int64(rands.Int(0, 24*3600)),
HeaderSize: 1024,
@@ -114,43 +120,46 @@ func TestMemoryList_Purge_Large_List(t *testing.T) {
}
func TestMemoryList_Stat(t *testing.T) {
list := NewMemoryList()
list := caches.NewMemoryList()
_ = list.Init()
_ = list.Add("a", &Item{
_ = list.Add("a", &caches.Item{
Key: "a1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("b", &Item{
_ = list.Add("b", &caches.Item{
Key: "b1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
_ = list.Add("c", &Item{
_ = list.Add("c", &caches.Item{
Key: "c1",
ExpiredAt: time.Now().Unix(),
HeaderSize: 1024,
})
_ = list.Add("d", &Item{
_ = list.Add("d", &caches.Item{
Key: "d1",
ExpiredAt: time.Now().Unix() - 2,
HeaderSize: 1024,
})
result, _ := list.Stat(func(hash string) bool {
// 随机测试
rand.Seed(time.Now().UnixNano())
return rand.Int()%2 == 0
})
t.Log(result)
}
func TestMemoryList_CleanPrefix(t *testing.T) {
list := NewMemoryList()
list := caches.NewMemoryList()
_ = list.Init()
before := time.Now()
for i := 0; i < 1_000_000; i++ {
var count = 100
if testutils.IsSingleTesting() {
count = 1_000_000
}
for i := 0; i < count; i++ {
key := "https://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html"
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
Key: key,
ExpiredAt: time.Now().Unix() + 3600,
BodySize: 0,
@@ -172,27 +181,69 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
t.Log(time.Since(before).Seconds()*1000, "ms")
}
func TestMapRandomDelete(t *testing.T) {
var countMap = map[int]int{} // k => count
var count = 1000
if testutils.IsSingleTesting() {
count = 1_000_000
}
for j := 0; j < count; j++ {
var m = map[int]bool{}
for i := 0; i < 100; i++ {
m[i] = true
}
var count = 0
for k := range m {
delete(m, k)
count++
if count >= 10 {
break
}
}
for k := range m {
countMap[k]++
}
}
var counts = []int{}
for _, count := range countMap {
counts = append(counts, count)
}
sort.Ints(counts)
t.Log("["+types.String(len(counts))+"]", counts)
}
func TestMemoryList_PurgeLFU(t *testing.T) {
var list = NewMemoryList().(*MemoryList)
list.minWeek = 2704
var list = caches.NewMemoryList().(*caches.MemoryList)
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
t.Log("current week:", currentWeek())
_ = list.Add("1", &caches.Item{})
_ = list.Add("2", &caches.Item{})
_ = list.Add("3", &caches.Item{})
_ = list.Add("4", &caches.Item{})
_ = list.Add("5", &caches.Item{})
_ = list.Add("1", &Item{})
_ = list.Add("2", &Item{})
_ = list.Add("3", &Item{})
_ = list.Add("4", &Item{})
_ = list.Add("5", &Item{})
_ = list.Add("6", &Item{Week: 2704})
_ = list.Add("7", &Item{Week: 2704})
_ = list.Add("8", &Item{Week: 2705})
//_ = list.IncreaseHit("1")
//_ = list.IncreaseHit("2")
//_ = list.IncreaseHit("3")
//_ = list.IncreaseHit("4")
//_ = list.IncreaseHit("5")
err := list.PurgeLFU(2, func(hash string) error {
count, err := list.Count()
if err != nil {
t.Fatal(err)
}
t.Log("count items before purge:", count)
err = list.PurgeLFU(5, func(hash string) error {
t.Log("purge lfu:", hash)
return nil
})
@@ -201,59 +252,40 @@ func TestMemoryList_PurgeLFU(t *testing.T) {
}
t.Log("ok")
logs.PrintAsJSON(list.weekItemMaps, t)
t.Log(list.Count())
}
func TestMemoryList_IncreaseHit(t *testing.T) {
var list = NewMemoryList().(*MemoryList)
var item = &Item{}
item.Week = 2705
item.Week2Hits = 100
_ = list.Add("a", &Item{})
_ = list.Add("a", item)
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
logs.PrintAsJSON(list.weekItemMaps, t)
_ = list.IncreaseHit("a")
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
logs.PrintAsJSON(list.weekItemMaps, t)
_ = list.IncreaseHit("a")
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
logs.PrintAsJSON(list.weekItemMaps, t)
count, err = list.Count()
if err != nil {
t.Fatal(err)
}
t.Log("count items left:", count)
}
func TestMemoryList_CleanAll(t *testing.T) {
var list = NewMemoryList().(*MemoryList)
var item = &Item{}
item.Week = 2705
item.Week2Hits = 100
_ = list.Add("a", &Item{})
var list = caches.NewMemoryList().(*caches.MemoryList)
_ = list.Add("a", &caches.Item{})
_ = list.CleanAll()
logs.PrintAsJSON(list.itemMaps, t)
logs.PrintAsJSON(list.weekItemMaps, t)
logs.PrintAsJSON(list.ItemMaps(), t)
t.Log(list.Count())
}
func TestMemoryList_GC(t *testing.T) {
list := NewMemoryList().(*MemoryList)
if !testutils.IsSingleTesting() {
return
}
list := caches.NewMemoryList().(*caches.MemoryList)
_ = list.Init()
for i := 0; i < 1_000_000; i++ {
key := "https://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html"
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
Key: key,
ExpiredAt: 0,
BodySize: 0,
HeaderSize: 0,
})
}
time.Sleep(10 * time.Second)
t.Log("clean...", len(list.itemMaps))
t.Log("clean...", len(list.ItemMaps()))
_ = list.CleanAll()
t.Log("cleanAll...", len(list.itemMaps))
t.Log("cleanAll...", len(list.ItemMaps()))
before := time.Now()
//runtime.GC()
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
@@ -266,3 +298,30 @@ func TestMemoryList_GC(t *testing.T) {
time.Sleep(30 * time.Minute)
}
}
func BenchmarkMemoryList(b *testing.B) {
var list = caches.NewMemoryList()
err := list.Init()
if err != nil {
b.Fatal(err)
}
for i := 0; i < 1_000_000; i++ {
_ = list.Add(stringutil.Md5(types.String(i)), &caches.Item{
Key: "a1",
ExpiredAt: time.Now().Unix() + 3600,
HeaderSize: 1024,
})
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000))))
_ = list.Add("a"+types.String(rands.Int(1, 100000)), &caches.Item{})
_, _ = list.Purge(1000, func(hash string) error {
return nil
})
}
})
}

View File

@@ -7,6 +7,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"golang.org/x/sys/unix"
@@ -35,6 +36,9 @@ type Manager struct {
SubDiskDirs []*serverconfigs.CacheDir
MaxMemoryCapacity *shared.SizeCapacity
CountFileStorages int
CountMemoryStorages int
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
storageMap map[int64]StorageInterface // policyId => *Storage
locker sync.RWMutex
@@ -143,6 +147,16 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
}
}
}
this.CountFileStorages = 0
this.CountFileStorages = 0
for _, storage := range this.storageMap {
_, isFileStorage := storage.(*FileStorage)
this.CountMemoryStorages++
if isFileStorage {
this.CountFileStorages++
}
}
}
// FindPolicy 获取Policy信息
@@ -172,6 +186,11 @@ func (this *Manager) NewStorageWithPolicy(policy *serverconfigs.HTTPCachePolicy)
return nil
}
// StorageMap 获取已有的存储对象
func (this *Manager) StorageMap() map[int64]StorageInterface {
return this.storageMap
}
// TotalDiskSize 消耗的磁盘尺寸
func (this *Manager) TotalDiskSize() int64 {
this.locker.RLock()
@@ -256,3 +275,33 @@ func (this *Manager) FindAllStorages() []StorageInterface {
}
return storages
}
// ScanGarbageCaches 清理目录中“失联”的缓存文件
func (this *Manager) ScanGarbageCaches(callback func(path string) error) error {
var storages = this.FindAllStorages()
for _, storage := range storages {
fileStorage, ok := storage.(*FileStorage)
if !ok {
continue
}
err := fileStorage.ScanGarbageCaches(callback)
if err != nil {
return err
}
}
return nil
}
// MaxSystemMemoryBytesPerStorage 计算单个策略能使用的系统最大内存
func (this *Manager) MaxSystemMemoryBytesPerStorage() int64 {
var count = this.CountMemoryStorages
if count < 1 {
count = 1
}
var resultBytes = int64(utils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory
if resultBytes < 1<<30 {
resultBytes = 1 << 30
}
return resultBytes
}

View File

@@ -1,8 +1,9 @@
package caches
package caches_test
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/Tea"
"testing"
)
@@ -10,7 +11,7 @@ import (
func TestManager_UpdatePolicies(t *testing.T) {
{
var policies = []*serverconfigs.HTTPCachePolicy{}
SharedManager.UpdatePolicies(policies)
caches.SharedManager.UpdatePolicies(policies)
printManager(t)
}
@@ -38,7 +39,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
},
},
}
SharedManager.UpdatePolicies(policies)
caches.SharedManager.UpdatePolicies(policies)
printManager(t)
}
@@ -66,7 +67,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
},
},
}
SharedManager.UpdatePolicies(policies)
caches.SharedManager.UpdatePolicies(policies)
printManager(t)
}
}
@@ -80,8 +81,8 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
},
}
SharedManager.UpdatePolicies(policies)
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
caches.SharedManager.UpdatePolicies(policies)
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
{
Id: 1,
Type: serverconfigs.CachePolicyStorageMemory,
@@ -102,8 +103,8 @@ func TestManager_ChangePolicy_File(t *testing.T) {
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
},
}
SharedManager.UpdatePolicies(policies)
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
caches.SharedManager.UpdatePolicies(policies)
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
{
Id: 1,
Type: serverconfigs.CachePolicyStorageFile,
@@ -115,10 +116,17 @@ func TestManager_ChangePolicy_File(t *testing.T) {
})
}
func TestManager_MaxSystemMemoryBytesPerStorage(t *testing.T) {
for i := 0; i < 100; i++ {
caches.SharedManager.CountMemoryStorages = i
t.Log(i, caches.SharedManager.MaxSystemMemoryBytesPerStorage()>>30, "GB")
}
}
func printManager(t *testing.T) {
t.Log("===manager==")
t.Log("storage:")
for _, storage := range SharedManager.storageMap {
for _, storage := range caches.SharedManager.StorageMap() {
t.Log(" storage:", storage.Policy().Id)
}
t.Log("===============")

View File

@@ -0,0 +1,375 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/iwind/TeaGo/logs"
"os"
"sync"
"sync/atomic"
"time"
)
const (
enableFragmentPool = false
minMemoryFragmentPoolItemSize = 8 << 10
maxMemoryFragmentPoolItemSize = 128 << 20
maxItemsInMemoryFragmentPoolBucket = 1024
memoryFragmentPoolBucketSegmentSize = 512 << 10
maxMemoryFragmentPoolItemAgeSeconds = 60
)
var SharedFragmentMemoryPool *MemoryFragmentPool
func init() {
if !teaconst.IsMain {
return
}
SharedFragmentMemoryPool = NewMemoryFragmentPool()
goman.New(func() {
var ticker = time.NewTicker(200 * time.Millisecond)
for range ticker.C {
for i := 0; i < 10; i++ { // skip N empty buckets
var isEmpty = SharedFragmentMemoryPool.GCNextBucket()
if !isEmpty {
break
}
}
}
})
}
type MemoryFragmentPoolItem struct {
Bytes []byte
size int64
createdAt int64
Refs int32
}
func (this *MemoryFragmentPoolItem) IsExpired() bool {
return this.createdAt < fasttime.Now().Unix()-maxMemoryFragmentPoolItemAgeSeconds
}
func (this *MemoryFragmentPoolItem) Reset() {
this.Bytes = nil
}
func (this *MemoryFragmentPoolItem) IsAvailable() bool {
return atomic.AddInt32(&this.Refs, 1) == 1
}
// MemoryFragmentPool memory fragments management
type MemoryFragmentPool struct {
bucketMaps []map[uint64]*MemoryFragmentPoolItem // [ { id => Zero }, ... ]
countBuckets int
gcBucketIndex int
mu sync.RWMutex
id uint64
totalMemory int64
isOk bool
capacity int64
debugMode bool
countGet uint64
countNew uint64
}
// NewMemoryFragmentPool create new fragment memory pool
func NewMemoryFragmentPool() *MemoryFragmentPool {
var pool = &MemoryFragmentPool{}
pool.init()
return pool
}
func (this *MemoryFragmentPool) init() {
var capacity = int64(utils.SystemMemoryGB()) << 30 / 16
if capacity > 256<<20 {
this.isOk = true
this.capacity = capacity
this.bucketMaps = []map[uint64]*MemoryFragmentPoolItem{}
for i := 0; i < maxMemoryFragmentPoolItemSize/memoryFragmentPoolBucketSegmentSize+1; i++ {
this.bucketMaps = append(this.bucketMaps, map[uint64]*MemoryFragmentPoolItem{})
}
this.countBuckets = len(this.bucketMaps)
}
// print statistics for debug
if len(os.Getenv("GOEDGE_DEBUG_MEMORY_FRAGMENT_POOL")) > 0 {
this.debugMode = true
go func() {
var maxRounds = 10_000
var ticker = time.NewTicker(10 * time.Second)
for range ticker.C {
logs.Println("reused:", this.countGet, "created:", this.countNew, "fragments:", this.Len(), "memory:", this.totalMemory>>20, "MB")
maxRounds--
if maxRounds <= 0 {
break
}
}
}()
}
}
// Get try to get a bytes object
func (this *MemoryFragmentPool) Get(expectSize int64) (resultBytes []byte, ok bool) {
if !this.isOk {
return
}
if expectSize <= 0 {
return
}
// DO NOT check min segment size
this.mu.RLock()
var bucketIndex = this.bucketIndexForSize(expectSize)
var resultItemId uint64
const maxSearchingBuckets = 20
for i := bucketIndex; i <= bucketIndex+maxSearchingBuckets; i++ {
resultBytes, resultItemId, ok = this.findItemInMap(this.bucketMaps[i], expectSize)
if ok {
this.mu.RUnlock()
// remove from bucket
this.mu.Lock()
delete(this.bucketMaps[i], resultItemId)
this.mu.Unlock()
return
}
if i >= this.countBuckets {
break
}
}
this.mu.RUnlock()
return
}
// Put a bytes object to specified bucket
func (this *MemoryFragmentPool) Put(data []byte) (ok bool) {
if !this.isOk {
return
}
var l = int64(cap(data)) // MUST be 'cap' instead of 'len'
if l < minMemoryFragmentPoolItemSize || l > maxMemoryFragmentPoolItemSize {
return
}
if atomic.LoadInt64(&this.totalMemory) >= this.capacity {
return
}
var itemId = atomic.AddUint64(&this.id, 1)
this.mu.Lock()
defer this.mu.Unlock()
var bucketMap = this.bucketMaps[this.bucketIndexForSize(l)]
if len(bucketMap) >= maxItemsInMemoryFragmentPoolBucket {
return
}
atomic.AddInt64(&this.totalMemory, l)
bucketMap[itemId] = &MemoryFragmentPoolItem{
Bytes: data,
size: l,
createdAt: fasttime.Now().Unix(),
}
return true
}
// GC fully GC
func (this *MemoryFragmentPool) GC() {
if !this.isOk {
return
}
var totalMemory = atomic.LoadInt64(&this.totalMemory)
if totalMemory < this.capacity {
return
}
this.mu.Lock()
defer this.mu.Unlock()
var garbageSize = totalMemory * 1 / 10 // 10%
// remove expired
for _, bucketMap := range this.bucketMaps {
for itemId, item := range bucketMap {
if item.IsExpired() {
delete(bucketMap, itemId)
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
garbageSize -= item.size
}
}
}
// remove others
if garbageSize > 0 {
for _, bucketMap := range this.bucketMaps {
for itemId, item := range bucketMap {
delete(bucketMap, itemId)
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
garbageSize -= item.size
if garbageSize <= 0 {
break
}
}
}
}
}
// GCNextBucket gc one bucket
func (this *MemoryFragmentPool) GCNextBucket() (isEmpty bool) {
if !this.isOk {
return
}
var itemIds = []uint64{}
// find
this.mu.RLock()
var bucketIndex = this.gcBucketIndex
var bucketMap = this.bucketMaps[bucketIndex]
isEmpty = len(bucketMap) == 0
if isEmpty {
this.mu.RUnlock()
// move to next bucket index
bucketIndex++
if bucketIndex >= this.countBuckets {
bucketIndex = 0
}
this.gcBucketIndex = bucketIndex
return
}
for itemId, item := range bucketMap {
if item.IsExpired() {
itemIds = append(itemIds, itemId)
}
}
this.mu.RUnlock()
// remove
if len(itemIds) > 0 {
this.mu.Lock()
for _, itemId := range itemIds {
item, ok := bucketMap[itemId]
if !ok {
continue
}
if !item.IsAvailable() {
continue
}
delete(bucketMap, itemId)
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
}
this.mu.Unlock()
}
// move to next bucket index
bucketIndex++
if bucketIndex >= this.countBuckets {
bucketIndex = 0
}
this.gcBucketIndex = bucketIndex
return
}
func (this *MemoryFragmentPool) SetCapacity(capacity int64) {
this.capacity = capacity
}
func (this *MemoryFragmentPool) TotalSize() int64 {
return atomic.LoadInt64(&this.totalMemory)
}
func (this *MemoryFragmentPool) Len() int {
this.mu.Lock()
defer this.mu.Unlock()
var count = 0
for _, bucketMap := range this.bucketMaps {
count += len(bucketMap)
}
return count
}
func (this *MemoryFragmentPool) IncreaseNew() {
if this.isOk && this.debugMode {
atomic.AddUint64(&this.countNew, 1)
}
}
func (this *MemoryFragmentPool) bucketIndexForSize(size int64) int {
return int(size / memoryFragmentPoolBucketSegmentSize)
}
func (this *MemoryFragmentPool) findItemInMap(bucketMap map[uint64]*MemoryFragmentPoolItem, expectSize int64) (resultBytes []byte, resultItemId uint64, ok bool) {
if len(bucketMap) == 0 {
return
}
for itemId, item := range bucketMap {
if item.size >= expectSize {
// check if is referred
if !item.IsAvailable() {
continue
}
// return result
if item.size != expectSize {
resultBytes = item.Bytes[:expectSize]
} else {
resultBytes = item.Bytes
}
// reset old item
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
resultItemId = itemId
if this.debugMode {
atomic.AddUint64(&this.countGet, 1)
}
ok = true
return
}
}
return
}

View File

@@ -0,0 +1,313 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestNewMemoryFragmentPool(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, 2<<20))
if !ok {
t.Log("finished at", i)
break
}
}
t.Log(pool.TotalSize()>>20, "MB", pool.Len(), "items")
{
r, ok := pool.Get(1 << 20)
a.IsTrue(ok)
a.IsTrue(len(r) == 1<<20)
}
{
r, ok := pool.Get(2 << 20)
a.IsTrue(ok)
a.IsTrue(len(r) == 2<<20)
}
{
r, ok := pool.Get(4 << 20)
a.IsFalse(ok)
a.IsTrue(len(r) == 0)
}
t.Log(pool.TotalSize()>>20, "MB", pool.Len(), "items")
}
func TestNewMemoryFragmentPool_LargeBucket(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
{
pool.Put(make([]byte, 128<<20+1))
a.IsTrue(pool.Len() == 0)
}
{
pool.Put(make([]byte, 128<<20))
a.IsTrue(pool.Len() == 1)
pool.Get(118 << 20)
a.IsTrue(pool.Len() == 0)
}
{
pool.Put(make([]byte, 128<<20))
a.IsTrue(pool.Len() == 1)
pool.Get(110 << 20)
a.IsTrue(pool.Len() == 1)
}
}
func TestMemoryFragmentPool_Get_Exactly(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
{
pool.Put(make([]byte, 129<<20))
a.IsTrue(pool.Len() == 0)
}
{
pool.Put(make([]byte, 4<<20))
a.IsTrue(pool.Len() == 1)
}
{
pool.Get(4 << 20)
a.IsTrue(pool.Len() == 0)
}
}
func TestMemoryFragmentPool_Get_Round(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
{
pool.Put(make([]byte, 8<<20))
pool.Put(make([]byte, 8<<20))
pool.Put(make([]byte, 8<<20))
a.IsTrue(pool.Len() == 3)
}
{
resultBytes, ok := pool.Get(3 << 20)
a.IsTrue(pool.Len() == 2)
if ok {
pool.Put(resultBytes)
}
}
{
pool.Get(2 << 20)
a.IsTrue(pool.Len() == 2)
}
{
pool.Get(1 << 20)
a.IsTrue(pool.Len() == 1)
}
}
func TestMemoryFragmentPool_GC(t *testing.T) {
var pool = caches.NewMemoryFragmentPool()
pool.SetCapacity(32 << 20)
for i := 0; i < 16; i++ {
pool.Put(make([]byte, 4<<20))
}
var before = time.Now()
pool.GC()
t.Log(time.Since(before).Seconds()*1000, "ms")
t.Log(pool.Len())
}
func TestMemoryFragmentPool_Memory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var pool = caches.NewMemoryFragmentPool()
testutils.StartMemoryStats(t, func() {
t.Log(pool.Len(), "items")
})
var sampleData = bytes.Repeat([]byte{'A'}, 16<<20)
var countNew = 0
for i := 0; i < 1000; i++ {
cacheData, ok := pool.Get(16 << 20)
if ok {
copy(cacheData, sampleData)
pool.Put(cacheData)
} else {
countNew++
var data = make([]byte, 16<<20)
copy(data, sampleData)
pool.Put(data)
}
}
t.Log("count new:", countNew)
t.Log("count remains:", pool.Len())
time.Sleep(10 * time.Minute)
}
func TestMemoryFragmentPool_GCNextBucket(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 1000; i++ {
pool.Put(make([]byte, rands.Int(0, 100)<<20))
}
var lastLen int
for {
pool.GCNextBucket()
var currentLen = pool.Len()
if lastLen == currentLen {
continue
}
lastLen = currentLen
t.Log(currentLen, "items", pool.TotalSize(), "bytes", timeutil.Format("H:i:s"))
time.Sleep(100 * time.Millisecond)
if currentLen == 0 {
break
}
}
}
func TestMemoryFragmentPoolItem(t *testing.T) {
var a = assert.NewAssertion(t)
var m = map[int]*caches.MemoryFragmentPoolItem{}
m[1] = &caches.MemoryFragmentPoolItem{
Refs: 0,
}
var item = m[1]
a.IsTrue(item.Refs == 0)
a.IsTrue(atomic.AddInt32(&item.Refs, 1) == 1)
for _, item2 := range m {
t.Log(item2)
a.IsTrue(atomic.AddInt32(&item2.Refs, 1) == 2)
}
t.Log(m)
}
func BenchmarkMemoryFragmentPool_Get_HIT(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, 2<<20))
if !ok {
break
}
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
data, ok := pool.Get(2 << 20)
if ok {
pool.Put(data)
}
}
})
}
func BenchmarkMemoryFragmentPool_Get_TOTALLY_MISSING(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, 2<<20+100))
if !ok {
break
}
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
data, ok := pool.Get(2<<20 + 200)
if ok {
pool.Put(data)
}
}
})
}
func BenchmarkMemoryPool_Get_HIT_MISSING(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, rands.Int(2, 32)<<20))
if !ok {
break
}
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
data, ok := pool.Get(4 << 20)
if ok {
pool.Put(data)
}
}
})
}
func BenchmarkMemoryFragmentPool_GC(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
pool.SetCapacity(1 << 30)
for i := 0; i < 2_000; i++ {
pool.Put(make([]byte, 1<<20))
}
var mu = sync.Mutex{}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
for i := 0; i < 100; i++ {
pool.GCNextBucket()
}
mu.Unlock()
}
})
}

View File

@@ -12,14 +12,16 @@ type OpenFile struct {
meta []byte
header []byte
version int64
size int64
}
func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64) *OpenFile {
func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64, size int64) *OpenFile {
return &OpenFile{
fp: fp,
meta: meta,
header: header,
version: version,
size: size,
}
}

View File

@@ -3,7 +3,9 @@
package caches
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
"github.com/fsnotify/fsnotify"
"github.com/iwind/TeaGo/logs"
@@ -14,26 +16,34 @@ import (
"time"
)
const (
maxOpenFileSize = 256 << 20
)
type OpenFileCache struct {
poolMap map[string]*OpenFilePool // file path => Pool
poolList *linkedlist.List
poolList *linkedlist.List[*OpenFilePool]
watcher *fsnotify.Watcher
locker sync.RWMutex
maxSize int
count int
maxCount int
capacitySize int64
count int
usedSize int64
}
func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
if maxSize <= 0 {
maxSize = 16384
func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
if maxCount <= 0 {
maxCount = 16384
}
var cache = &OpenFileCache{
maxSize: maxSize,
poolMap: map[string]*OpenFilePool{},
poolList: linkedlist.NewList(),
maxCount: maxCount,
poolMap: map[string]*OpenFilePool{},
poolList: linkedlist.NewList[*OpenFilePool](),
capacitySize: (int64(utils.SystemMemoryGB()) << 30) / 16,
}
watcher, err := fsnotify.NewWatcher()
@@ -58,24 +68,36 @@ func (this *OpenFileCache) Get(filename string) *OpenFile {
pool, ok := this.poolMap[filename]
this.locker.RUnlock()
if ok {
file, consumed := pool.Get()
file, consumed, consumedSize := pool.Get()
if consumed {
this.locker.Lock()
this.count--
this.usedSize -= consumedSize
// pool如果为空也不需要从列表中删除避免put时需要重新创建
this.locker.Unlock()
}
return file
}
return nil
}
func (this *OpenFileCache) Put(filename string, file *OpenFile) {
if file.size > maxOpenFileSize {
return
}
this.locker.Lock()
defer this.locker.Unlock()
// 如果超过当前容量,则关闭最早的
if this.count >= this.maxCount || this.usedSize+file.size >= this.capacitySize {
this.consumeHead()
return
}
pool, ok := this.poolMap[filename]
var success bool
if ok {
@@ -92,35 +114,7 @@ func (this *OpenFileCache) Put(filename string, file *OpenFile) {
// 检查长度
if success {
this.count++
// 如果超过当前容量,则关闭最早的
if this.count > this.maxSize {
var delta = this.maxSize / 100 // 清理1%
if delta == 0 {
delta = 1
}
for i := 0; i < delta; i++ {
var head = this.poolList.Head()
if head == nil {
break
}
var headPool = head.Value.(*OpenFilePool)
headFile, consumed := headPool.Get()
if consumed {
this.count--
if headFile != nil {
_ = headFile.Close()
}
}
if headPool.Len() == 0 {
delete(this.poolMap, headPool.filename)
this.poolList.Remove(head)
_ = this.watcher.Remove(headPool.filename)
}
}
}
this.usedSize += file.size
}
}
@@ -136,6 +130,7 @@ func (this *OpenFileCache) Close(filename string) {
this.poolList.Remove(pool.linkItem)
_ = this.watcher.Remove(filename)
this.count -= pool.Len()
this.usedSize -= pool.usedSize
}
this.locker.Unlock()
@@ -155,18 +150,55 @@ func (this *OpenFileCache) CloseAll() {
this.poolList.Reset()
_ = this.watcher.Close()
this.count = 0
this.usedSize = 0
this.locker.Unlock()
}
func (this *OpenFileCache) SetCapacity(capacityBytes int64) {
this.capacitySize = capacityBytes
}
func (this *OpenFileCache) Debug() {
var ticker = time.NewTicker(5 * time.Second)
goman.New(func() {
for range ticker.C {
logs.Println("==== " + types.String(this.count) + " ====")
this.poolList.Range(func(item *linkedlist.Item) (goNext bool) {
logs.Println(filepath.Base(item.Value.(*OpenFilePool).Filename()), item.Value.(*OpenFilePool).Len())
logs.Println("==== " + types.String(this.count) + ", " + fmt.Sprintf("%.4fMB", float64(this.usedSize)/(1<<20)) + " ====")
this.poolList.Range(func(item *linkedlist.Item[*OpenFilePool]) (goNext bool) {
logs.Println(filepath.Base(item.Value.Filename()), item.Value.Len())
return true
})
}
})
}
func (this *OpenFileCache) consumeHead() {
var delta = 1
if this.count > 100 {
delta = 2
}
for i := 0; i < delta; i++ {
var head = this.poolList.Head()
if head == nil {
break
}
var headPool = head.Value
headFile, consumed, consumedSize := headPool.Get()
if consumed {
this.count--
this.usedSize -= consumedSize
if headFile != nil {
_ = headFile.Close()
}
}
if headPool.Len() == 0 {
delete(this.poolMap, headPool.filename)
this.poolList.Remove(head)
_ = this.watcher.Remove(headPool.filename)
}
}
}

View File

@@ -5,6 +5,7 @@ package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/types"
"testing"
"time"
)
@@ -15,13 +16,14 @@ func TestNewOpenFileCache_Close(t *testing.T) {
t.Fatal(err)
}
cache.Debug()
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
cache.Get("b.txt")
cache.Get("d.txt")
cache.Get("d.txt") // not exist
cache.Close("a.txt")
if testutils.IsSingleTesting() {
@@ -29,18 +31,39 @@ func TestNewOpenFileCache_Close(t *testing.T) {
}
}
func TestNewOpenFileCache_OverSize(t *testing.T) {
cache, err := caches.NewOpenFileCache(1024)
if err != nil {
t.Fatal(err)
}
cache.SetCapacity(1 << 30)
cache.Debug()
for i := 0; i < 100; i++ {
cache.Put("a"+types.String(i)+".txt", caches.NewOpenFile(nil, nil, nil, 0, 128<<20))
}
if testutils.IsSingleTesting() {
time.Sleep(100 * time.Second)
}
}
func TestNewOpenFileCache_CloseAll(t *testing.T) {
cache, err := caches.NewOpenFileCache(1024)
if err != nil {
t.Fatal(err)
}
cache.Debug()
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
cache.Get("b.txt")
cache.Get("d.txt")
cache.CloseAll()
time.Sleep(6 * time.Second)
if testutils.IsSingleTesting() {
time.Sleep(6 * time.Second)
}
}

View File

@@ -9,10 +9,11 @@ import (
type OpenFilePool struct {
c chan *OpenFile
linkItem *linkedlist.Item
linkItem *linkedlist.Item[*OpenFilePool]
filename string
version int64
isClosed bool
usedSize int64
}
func NewOpenFilePool(filename string) *OpenFilePool {
@@ -21,7 +22,7 @@ func NewOpenFilePool(filename string) *OpenFilePool {
c: make(chan *OpenFile, 1024),
version: fasttime.Now().UnixMilli(),
}
pool.linkItem = linkedlist.NewItem(pool)
pool.linkItem = linkedlist.NewItem[*OpenFilePool](pool)
return pool
}
@@ -29,27 +30,29 @@ func (this *OpenFilePool) Filename() string {
return this.filename
}
func (this *OpenFilePool) Get() (*OpenFile, bool) {
func (this *OpenFilePool) Get() (resultFile *OpenFile, consumed bool, consumedSize int64) {
// 如果已经关闭,直接返回
if this.isClosed {
return nil, false
return nil, false, 0
}
select {
case file := <-this.c:
if file != nil {
this.usedSize -= file.size
err := file.SeekStart()
if err != nil {
_ = file.Close()
return nil, true
return nil, true, file.size
}
file.version = this.version
return file, true
return file, true, file.size
}
return nil, false
return nil, false, 0
default:
return nil, false
return nil, false, 0
}
}
@@ -69,6 +72,7 @@ func (this *OpenFilePool) Put(file *OpenFile) bool {
// 加入Pool
select {
case this.c <- file:
this.usedSize += file.size
return true
default:
// 多余的直接关闭
@@ -81,6 +85,10 @@ func (this *OpenFilePool) Len() int {
return len(this.c)
}
func (this *OpenFilePool) TotalSize() int64 {
return this.usedSize
}
func (this *OpenFilePool) SetClosing() {
this.isClosed = true
}

View File

@@ -13,15 +13,15 @@ func TestOpenFilePool_Get(t *testing.T) {
var pool = caches.NewOpenFilePool("a")
t.Log(pool.Filename())
t.Log(pool.Get())
t.Log(pool.Put(caches.NewOpenFile(nil, nil, nil, 0)))
t.Log(pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1)))
t.Log(pool.Get())
t.Log(pool.Get())
}
func TestOpenFilePool_Close(t *testing.T) {
var pool = caches.NewOpenFilePool("a")
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
pool.Close()
}
@@ -35,7 +35,7 @@ func TestOpenFilePool_Concurrent(t *testing.T) {
defer wg.Done()
if rands.Int(0, 1) == 1 {
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
}
if rands.Int(0, 1) == 0 {
pool.Get()

View File

@@ -366,7 +366,7 @@ func (this *FileReader) Close() error {
} else {
var cacheMeta = make([]byte, len(this.meta))
copy(cacheMeta, this.meta)
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, cacheMeta, this.header, this.LastModified()))
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, cacheMeta, this.header, this.LastModified(), this.bodySize))
}
return nil
}

View File

@@ -20,7 +20,7 @@ type PartialFileReader struct {
func NewPartialFileReader(fp *os.File) *PartialFileReader {
return &PartialFileReader{
FileReader: NewFileReader(fp),
rangePath: partialRangesFilePath(fp.Name()),
rangePath: PartialRangesFilePath(fp.Name()),
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
@@ -23,6 +24,7 @@ import (
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
timeutil "github.com/iwind/TeaGo/utils/time"
"github.com/iwind/gosock/pkg/gosock"
"github.com/shirou/gopsutil/v3/load"
"math"
"os"
@@ -58,6 +60,8 @@ const (
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
FileTmpSuffix = ".tmp"
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间
HashKeyLength = 32
)
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
@@ -85,7 +89,8 @@ type FileStorage struct {
openFileCache *OpenFileCache
mainDiskIsFull bool
mainDiskIsFull bool
mainDiskTotalSize uint64
subDirs []*FileDir
}
@@ -106,6 +111,15 @@ func (this *FileStorage) Policy() *serverconfigs.HTTPCachePolicy {
// CanUpdatePolicy 检查策略是否可以更新
func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
if newPolicy == nil {
return false
}
// 检查类型
if newPolicy.Type != serverconfigs.CachePolicyStorageFile {
return false
}
// 检查路径是否有变化
oldOptionsJSON, err := json.Marshal(this.policy.Options)
if err != nil {
@@ -418,6 +432,13 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
return nil, ErrEntityTooLarge
}
// 检查磁盘是否超出容量
// 需要在内存缓存之前执行,避免成功写进到了内存缓存,但无法刷到磁盘
var capacityBytes = this.diskCapacityBytes()
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize()+(32<<20 /** 余量 **/) {
return nil, NewCapacityError("write file cache failed: over disk size, current: " + types.String(this.TotalDiskSize()) + ", capacity: " + types.String(capacityBytes))
}
// 先尝试内存缓存
// 我们限定仅小文件优先存在内存中
var maxMemorySize = FileToMemoryMaxSize
@@ -432,7 +453,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
}
// 如果队列满了,则等待
if err == ErrWritingQueueFull {
if errors.Is(err, ErrWritingQueueFull) {
return nil, err
}
}
@@ -443,7 +464,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
_, ok := sharedWritingFileKeyMap[key]
if ok {
sharedWritingFileKeyLocker.Unlock()
return nil, ErrFileIsWriting
return nil, fmt.Errorf("%w(001)", ErrFileIsWriting)
}
if !isFlushing && !fsutils.WriteReady() {
@@ -461,12 +482,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
}
}()
// 检查是否超出容量
var capacityBytes = this.diskCapacityBytes()
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize() {
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + types.String(this.TotalDiskSize()) + " bytes, capacity: " + types.String(capacityBytes))
}
var hash = stringutil.Md5(key)
dir, diskIsFull := this.subDir(hash)
@@ -490,7 +505,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
// 检查两次写入缓存的时间是否过于相近,分片内容不受此限制
if err == nil && !isPartial && time.Since(stat.ModTime()) <= 1*time.Second {
// 防止并发连续写入
return nil, ErrFileIsWriting
return nil, fmt.Errorf("%w(002)", ErrFileIsWriting)
}
// 构造文件名
@@ -556,7 +571,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
if isNewCreated && existsFile {
flags |= os.O_TRUNC
}
fsutils.WriteBegin()
writer, err := os.OpenFile(tmpPath, flags, 0666)
fsutils.WriteEnd()
if err != nil {
// TODO 检查在各个系统中的稳定性
if os.IsNotExist(err) {
@@ -589,7 +606,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
err = syscall.Flock(int(writer.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
removeOnFailure = false
return nil, ErrFileIsWriting
return nil, fmt.Errorf("%w (003)", ErrFileIsWriting)
}
var metaBodySize int64 = -1
@@ -890,7 +907,7 @@ func (this *FileStorage) Stop() {
// TotalDiskSize 消耗的磁盘尺寸
func (this *FileStorage) TotalDiskSize() int64 {
stat, err := fsutils.StatCache(this.options.Dir)
stat, err := fsutils.StatDeviceCache(this.options.Dir)
if err == nil {
return int64(stat.UsedSize())
}
@@ -930,7 +947,7 @@ func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFu
// 获取Hash对应的文件路径
func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
if len(hash) != 32 {
if len(hash) != HashKeyLength {
return "", false
}
var dir string
@@ -994,28 +1011,31 @@ func (this *FileStorage) initList() error {
// 清理任务
// TODO purge每个分区
func (this *FileStorage) purgeLoop() {
// 检查磁盘剩余空间
this.checkDiskSpace()
// load
systemLoad, _ := load.Avg()
// TODO 计算平均最近每日新增用量
// 计算是否应该开启LFU清理
var capacityBytes = this.diskCapacityBytes()
var startLFU = false
var requireFullLFU = false // 是否需要完整执行LFU
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
if lfuFreePercent <= 0 {
lfuFreePercent = 5
}
var hasFullDisk = this.mainDiskIsFull
if !hasFullDisk {
var subDirs = this.subDirs // copy slice
for _, subDir := range subDirs {
if subDir.IsFull {
hasFullDisk = true
break
if systemLoad == nil || systemLoad.Load5 > 10 {
// 2TB级别以上
if capacityBytes>>30 > 2000 {
lfuFreePercent = 100 /** GB **/ / float32(capacityBytes>>30) * 100 /** % **/
if lfuFreePercent > 3 {
lfuFreePercent = 3
}
}
}
}
var hasFullDisk = this.hasFullDisk()
if hasFullDisk {
startLFU = true
} else {
@@ -1034,30 +1054,45 @@ func (this *FileStorage) purgeLoop() {
var times = 1
// 空闲时间多清理
systemLoad, _ := load.Avg()
if systemLoad != nil {
if systemLoad.Load5 < 2 {
if systemLoad.Load5 < 3 {
times = 5
} else if systemLoad.Load5 < 3 {
times = 3
} else if systemLoad.Load5 < 5 {
times = 3
} else if systemLoad.Load5 < 10 {
times = 2
}
}
// 高速硬盘多清理
if fsutils.DiskIsExtremelyFast() {
times *= 8
} else if fsutils.DiskIsFast() {
times *= 4
}
// 处于LFU阈值时多清理
if startLFU {
times = 5
times *= 5
}
var purgeCount = this.policy.PersistenceAutoPurgeCount
if purgeCount <= 0 {
purgeCount = 1000
if fsutils.DiskIsExtremelyFast() {
purgeCount = 4000
} else if fsutils.DiskIsFast() {
purgeCount = 2000
}
}
for i := 0; i < times; i++ {
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
path, _ := this.hashPath(hash)
fsutils.WriteBegin()
err := this.removeCacheFile(path)
fsutils.WriteEnd()
if err != nil && !os.IsNotExist(err) {
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
}
@@ -1070,6 +1105,10 @@ func (this *FileStorage) purgeLoop() {
}
if countFound < purgeCount {
if i == 0 && startLFU {
requireFullLFU = true
}
break
}
@@ -1079,28 +1118,61 @@ func (this *FileStorage) purgeLoop() {
// 磁盘空间不足时,清除老旧的缓存
if startLFU {
var maxCount = 1000
var maxLoops = 5
if fsutils.DiskIsExtremelyFast() {
maxCount = 4000
} else if fsutils.DiskIsFast() {
maxCount = 2000
}
var total, _ = this.list.Count()
if total > 0 {
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
if count > 0 {
// 限制单次清理的条数,防止占用太多系统资源
if count > 2000 {
count = 2000
for {
maxLoops--
if maxLoops <= 0 {
break
}
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
// 开始清理
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
if count <= 0 {
break
}
// 限制单次清理的条数,防止占用太多系统资源
if count > maxCount {
count = maxCount
}
var before = time.Now()
err := this.list.PurgeLFU(count, func(hash string) error {
path, _ := this.hashPath(hash)
fsutils.WriteBegin()
err := this.removeCacheFile(path)
fsutils.WriteEnd()
if err != nil && !os.IsNotExist(err) {
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
}
return nil
})
var prefix = ""
if requireFullLFU {
prefix = "fully "
}
remotelogs.Println("CACHE", prefix+"LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count)+", cost: "+fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000))
if err != nil {
remotelogs.Warn("CACHE", "purge file storage in LFU failed: "+err.Error())
}
// 检查硬盘空间状态
if !requireFullLFU && !this.hasFullDisk() {
break
}
}
}
}
@@ -1108,11 +1180,16 @@ func (this *FileStorage) purgeLoop() {
// 热点数据任务
func (this *FileStorage) hotLoop() {
var memoryStorage = this.memoryStorage
var memoryStorage = this.memoryStorage // copy
if memoryStorage == nil {
return
}
// check memory space size
if !memoryStorage.HasFreeSpaceForHotItems() {
return
}
this.hotMapLocker.Lock()
if len(this.hotMap) == 0 {
this.hotMapLocker.Unlock()
@@ -1124,6 +1201,9 @@ func (this *FileStorage) hotLoop() {
var result = []*HotItem{} // [ {key: ..., hits: ...}, ... ]
for _, v := range this.hotMap {
if v.Hits <= 1 {
continue
}
result = append(result, v)
}
@@ -1227,12 +1307,21 @@ func (this *FileStorage) hotLoop() {
func (this *FileStorage) diskCapacityBytes() int64 {
var c1 = this.policy.CapacityBytes()
if SharedManager.MaxDiskCapacity != nil {
var c2 = SharedManager.MaxDiskCapacity.Bytes()
var nodeCapacity = SharedManager.MaxDiskCapacity // copy
if nodeCapacity != nil {
var c2 = nodeCapacity.Bytes()
if c2 > 0 {
if this.mainDiskTotalSize > 0 && c2 >= int64(this.mainDiskTotalSize) {
c2 = int64(this.mainDiskTotalSize) * 95 / 100 // keep 5% free
}
return c2
}
}
if c1 <= 0 || (this.mainDiskTotalSize > 0 && c1 >= int64(this.mainDiskTotalSize)) {
c1 = int64(this.mainDiskTotalSize) * 95 / 100 // keep 5% free
}
return c1
}
@@ -1291,19 +1380,9 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
if rate <= 0 {
rate = 1000
}
if this.lastHotSize == 0 {
// 自动降低采样率来增加热点数据的缓存几率
rate = rate / 10
}
if rands.Int(0, rate) == 0 {
var memoryStorage = this.memoryStorage
var hitErr = this.list.IncreaseHit(hash)
if hitErr != nil {
// 此错误可以忽略
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
}
// 增加到热点
// 这里不收录缓存尺寸过大的文件
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*sizes.M {
@@ -1319,6 +1398,15 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
}
}
this.hotMapLocker.Unlock()
// 只有重复点击的才增加点击量
if ok {
var hitErr = this.list.IncreaseHit(hash)
if hitErr != nil {
// 此错误可以忽略
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
}
}
}
}
}
@@ -1335,11 +1423,15 @@ func (this *FileStorage) removeCacheFile(path string) error {
err = nil
// 删除Partial相关
var partialPath = partialRangesFilePath(path)
var partialPath = PartialRangesFilePath(path)
if openFileCache != nil {
openFileCache.Close(partialPath)
}
_ = os.Remove(partialPath)
_, statErr := os.Stat(partialPath)
if statErr == nil {
_ = os.Remove(partialPath)
}
}
return err
}
@@ -1418,7 +1510,7 @@ func (this *FileStorage) initOpenFileCache() {
}
func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStorage)) {
var memoryStorage = this.memoryStorage
var memoryStorage = this.memoryStorage // copy
if memoryStorage != nil {
f(memoryStorage)
}
@@ -1434,20 +1526,56 @@ func (this *FileStorage) checkDiskSpace() {
}
if options != nil && len(options.Dir) > 0 {
stat, err := fsutils.Stat(options.Dir)
stat, err := fsutils.StatDevice(options.Dir)
if err == nil {
this.mainDiskIsFull = stat.FreeSize() < minFreeSize
this.mainDiskTotalSize = stat.TotalSize()
// check capacity (only on main directory) when node capacity had not been set
if !this.mainDiskIsFull {
var capacityBytes int64
var maxDiskCapacity = SharedManager.MaxDiskCapacity // copy
if maxDiskCapacity != nil && maxDiskCapacity.Bytes() > 0 {
capacityBytes = SharedManager.MaxDiskCapacity.Bytes()
} else {
var policy = this.policy // copy
if policy != nil {
capacityBytes = policy.CapacityBytes() // copy
}
}
if capacityBytes > 0 && stat.UsedSize() >= uint64(capacityBytes) {
this.mainDiskIsFull = true
}
}
}
}
var subDirs = this.subDirs // copy slice
for _, subDir := range subDirs {
stat, err := fsutils.Stat(subDir.Path)
stat, err := fsutils.StatDevice(subDir.Path)
if err == nil {
subDir.IsFull = stat.FreeSize() < minFreeSize
}
}
}
// 检查是否有已满的磁盘分区
func (this *FileStorage) hasFullDisk() bool {
this.checkDiskSpace()
var hasFullDisk = this.mainDiskIsFull
if !hasFullDisk {
var subDirs = this.subDirs // copy slice
for _, subDir := range subDirs {
if subDir.IsFull {
hasFullDisk = true
break
}
}
}
return hasFullDisk
}
// 获取目录
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
@@ -1477,6 +1605,121 @@ func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
return subDir.Path + suffix, subDir.IsFull
}
// ScanGarbageCaches 清理目录中“失联”的缓存文件
// “失联”为不在HashMap中的文件
func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error) error {
if !this.list.(*FileList).HashMapIsLoaded() {
return errors.New("cache list is loading")
}
var mainDir = this.options.Dir
var allDirs = []string{mainDir}
var subDirs = this.subDirs // copy
for _, subDir := range subDirs {
allDirs = append(allDirs, subDir.Path)
}
var countDirs = 0
// process progress
var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
_, sockErr := progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{"progress": 0}}, 1*time.Second)
var canReportProgress = sockErr == nil
var lastProgress float64
var countFound = 0
for _, subDir := range allDirs {
var dir0 = subDir + "/p" + types.String(this.policy.Id)
dir1Matches, err := filepath.Glob(dir0 + "/*")
if err != nil {
// ignore error
continue
}
for _, dir1 := range dir1Matches {
if len(filepath.Base(dir1)) != 2 {
continue
}
dir2Matches, err := filepath.Glob(dir1 + "/*")
if err != nil {
// ignore error
continue
}
for _, dir2 := range dir2Matches {
if len(filepath.Base(dir2)) != 2 {
continue
}
countDirs++
// report progress
if canReportProgress {
var progress = float64(countDirs) / 65536
if fmt.Sprintf("%.2f", lastProgress) != fmt.Sprintf("%.2f", progress) {
lastProgress = progress
_, _ = progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{
"progress": progress,
"count": countFound,
}}, 100*time.Millisecond)
}
}
fileMatches, err := filepath.Glob(dir2 + "/*.cache")
if err != nil {
// ignore error
continue
}
for _, file := range fileMatches {
var filename = filepath.Base(file)
var hash = strings.TrimSuffix(filename, ".cache")
if len(hash) != HashKeyLength {
continue
}
isReady, found := this.list.(*FileList).ExistQuick(hash)
if !isReady {
continue
}
if found {
continue
}
// 检查文件正在被写入
stat, err := os.Stat(file)
if err != nil {
continue
}
if fasttime.Now().Unix()-stat.ModTime().Unix() < 300 /** 5 minutes **/ {
continue
}
if fileCallback != nil {
countFound++
err = fileCallback(file)
if err != nil {
return err
}
}
}
}
}
}
// 100% progress
if canReportProgress && lastProgress != 1 {
_, _ = progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{
"progress": 1,
"count": countFound,
}}, 100*time.Millisecond)
}
return nil
}
// 计算字节数字代号
func (this *FileStorage) charCode(r byte) uint8 {
if r >= '0' && r <= '9' {
return r - '0'

View File

@@ -5,6 +5,7 @@ import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs"
@@ -18,6 +19,10 @@ import (
)
func TestFileStorage_Init(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -48,6 +53,10 @@ func TestFileStorage_Init(t *testing.T) {
}
func TestFileStorage_OpenWriter(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -95,6 +104,10 @@ func TestFileStorage_OpenWriter(t *testing.T) {
}
func TestFileStorage_OpenWriter_Partial(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 2,
IsOn: true,
@@ -133,6 +146,10 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
}
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -201,6 +218,10 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
}
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -230,7 +251,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, -1, false)
if err != nil {
if err != ErrFileIsWriting {
if errors.Is(err, ErrFileIsWriting) {
t.Error(err)
return
}
@@ -259,6 +280,10 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
}
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -288,7 +313,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, -1, false)
if err != nil {
if err != ErrFileIsWriting {
if errors.Is(err, ErrFileIsWriting) {
t.Error(err)
return
}
@@ -318,6 +343,10 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
}
func TestFileStorage_Read(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -357,6 +386,10 @@ func TestFileStorage_Read(t *testing.T) {
}
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -413,6 +446,10 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
}
func TestFileStorage_Read_NotFound(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -449,6 +486,10 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
}
func TestFileStorage_Delete(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -471,6 +512,10 @@ func TestFileStorage_Delete(t *testing.T) {
}
func TestFileStorage_Stat(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -499,6 +544,10 @@ func TestFileStorage_Stat(t *testing.T) {
}
func TestFileStorage_CleanAll(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -533,6 +582,10 @@ func TestFileStorage_CleanAll(t *testing.T) {
}
func TestFileStorage_Stop(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -551,6 +604,10 @@ func TestFileStorage_Stop(t *testing.T) {
}
func TestFileStorage_DecodeFile(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1,
IsOn: true,
@@ -570,6 +627,10 @@ func TestFileStorage_DecodeFile(t *testing.T) {
}
func TestFileStorage_RemoveCacheFile(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(nil)
defer storage.Stop()
@@ -577,6 +638,29 @@ func TestFileStorage_RemoveCacheFile(t *testing.T) {
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
}
func TestFileStorage_ScanGarbageCaches(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 43,
Options: map[string]any{"dir": "/Users/WorkSpace/EdgeProject/EdgeCache"},
})
err := storage.Init()
if err != nil {
t.Fatal(err)
}
err = storage.ScanGarbageCaches(func(path string) error {
t.Log(path, PartialRangesFilePath(path))
return nil
})
if err != nil {
t.Fatal(err)
}
}
func BenchmarkFileStorage_Read(b *testing.B) {
runtime.GOMAXPROCS(1)

View File

@@ -1,6 +1,7 @@
package caches
import (
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -9,12 +10,9 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/cespare/xxhash"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"github.com/shirou/gopsutil/v3/load"
"math"
"runtime"
"strconv"
@@ -31,6 +29,11 @@ type MemoryItem struct {
Status int
IsDone bool
ModifiedAt int64
IsPrepared bool
WriteOffset int64
isReferring bool // if it is referring by other objects
}
func (this *MemoryItem) IsExpired() bool {
@@ -51,7 +54,7 @@ type MemoryStorage struct {
purgeTicker *utils.Ticker
totalSize int64
usedSize int64
writingKeyMap map[string]zero.Zero // key => bool
ignoreKeys *setutils.FixedSet
@@ -63,7 +66,7 @@ func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage Stora
if parentStorage != nil {
if queueSize <= 0 {
queueSize = 2048 + int(policy.CapacityBytes()/sizes.G)*2048
queueSize = utils.SystemMemoryGB() * 100_000
}
dirtyChan = make(chan string, queueSize)
@@ -86,10 +89,10 @@ func (this *MemoryStorage) Init() error {
_ = this.list.Init()
this.list.OnAdd(func(item *Item) {
atomic.AddInt64(&this.totalSize, item.TotalSize())
atomic.AddInt64(&this.usedSize, item.TotalSize())
})
this.list.OnRemove(func(item *Item) {
atomic.AddInt64(&this.totalSize, -item.TotalSize())
atomic.AddInt64(&this.usedSize, -item.TotalSize())
})
this.initPurgeTicker()
@@ -122,7 +125,12 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
// read from valuesMap
this.locker.RLock()
item := this.valuesMap[hash]
var item = this.valuesMap[hash]
if item != nil {
item.isReferring = true
}
if item == nil || !item.IsDone {
this.locker.RUnlock()
return nil, ErrNotFound
@@ -137,17 +145,6 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
}
this.locker.RUnlock()
// 增加点击量
// 1/1000采样
// TODO 考虑是否在缓存策略里设置
if rands.Int(0, 1000) == 0 {
var hitErr = this.list.IncreaseHit(types.String(hash))
if hitErr != nil {
// 此错误可以忽略
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
}
}
return reader, nil
}
this.locker.RUnlock()
@@ -165,7 +162,7 @@ func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, h
// TODO 内存缓存暂时不支持分块内容存储
if isPartial {
return nil, ErrFileIsWriting
return nil, fmt.Errorf("%w (004)", ErrFileIsWriting)
}
return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
}
@@ -180,7 +177,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
if isDirty &&
this.parentStorage != nil &&
this.dirtyQueueSize > 0 &&
len(this.dirtyChan) == this.dirtyQueueSize { // 缓存时间过长
len(this.dirtyChan) >= this.dirtyQueueSize-int(fsutils.DiskMaxWrites) /** delta **/ { // 缓存时间过长
return nil, ErrWritingQueueFull
}
@@ -191,7 +188,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
var isWriting = false
_, ok := this.writingKeyMap[key]
if ok {
return nil, ErrFileIsWriting
return nil, fmt.Errorf("%w (005)", ErrFileIsWriting)
}
this.writingKeyMap[key] = zero.New()
defer func() {
@@ -212,17 +209,17 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
_ = this.list.Remove(hashString)
item = nil
} else {
return nil, ErrFileIsWriting
return nil, fmt.Errorf("%w (006)", ErrFileIsWriting)
}
}
// 检查是否超出最大值
capacityBytes := this.memoryCapacityBytes()
// 检查是否超出容量最大值
var capacityBytes = this.memoryCapacityBytes()
if bodySize < 0 {
bodySize = 0
}
if capacityBytes > 0 && capacityBytes <= this.totalSize+bodySize {
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
if capacityBytes > 0 && capacityBytes <= atomic.LoadInt64(&this.usedSize)+bodySize {
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.usedSize, 10) + " bytes")
}
// 先删除
@@ -232,7 +229,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
}
isWriting = true
return NewMemoryWriter(this, key, expiresAt, status, isDirty, maxSize, func() {
return NewMemoryWriter(this, key, expiresAt, status, isDirty, bodySize, maxSize, func(valueItem *MemoryItem) {
this.locker.Lock()
delete(this.writingKeyMap, key)
this.locker.Unlock()
@@ -264,7 +261,7 @@ func (this *MemoryStorage) CleanAll() error {
this.locker.Lock()
this.valuesMap = map[uint64]*MemoryItem{}
_ = this.list.Reset()
atomic.StoreInt64(&this.totalSize, 0)
atomic.StoreInt64(&this.usedSize, 0)
this.locker.Unlock()
return nil
}
@@ -370,11 +367,16 @@ func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy
// CanUpdatePolicy 检查策略是否可以更新
func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
return true
return newPolicy != nil && newPolicy.Type == serverconfigs.CachePolicyStorageMemory
}
// AddToList 将缓存添加到列表
func (this *MemoryStorage) AddToList(item *Item) {
// skip added item
if item.MetaSize > 0 {
return
}
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
var hash = types.String(this.hash(item.Key))
@@ -392,7 +394,7 @@ func (this *MemoryStorage) TotalDiskSize() int64 {
// TotalMemorySize 内存尺寸
func (this *MemoryStorage) TotalMemorySize() int64 {
return atomic.LoadInt64(&this.totalSize)
return atomic.LoadInt64(&this.usedSize)
}
// IgnoreKey 忽略某个Key即不缓存某个Key
@@ -405,6 +407,11 @@ func (this *MemoryStorage) CanSendfile() bool {
return false
}
// HasFreeSpaceForHotItems 是否有足够的空间提供给热门内容
func (this *MemoryStorage) HasFreeSpaceForHotItems() bool {
return atomic.LoadInt64(&this.usedSize) < this.memoryCapacityBytes()*3/4
}
// 计算Key Hash
func (this *MemoryStorage) hash(key string) uint64 {
return xxhash.Sum64String(key)
@@ -412,22 +419,6 @@ func (this *MemoryStorage) hash(key string) uint64 {
// 清理任务
func (this *MemoryStorage) purgeLoop() {
// 计算是否应该开启LFU清理
var capacityBytes = this.policy.CapacityBytes()
var startLFU = false
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
var lfuFreePercent = this.policy.MemoryLFUFreePercent
if lfuFreePercent <= 0 {
lfuFreePercent = 5
}
if capacityBytes > 0 {
if lfuFreePercent < 100 {
if usedPercent >= 100-lfuFreePercent {
startLFU = true
}
}
}
// 清理过期
var purgeCount = this.policy.MemoryAutoPurgeCount
if purgeCount <= 0 {
@@ -444,6 +435,23 @@ func (this *MemoryStorage) purgeLoop() {
})
// LFU
// 计算是否应该开启LFU清理
var capacityBytes = this.policy.CapacityBytes()
var startLFU = false
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
var lfuFreePercent = this.policy.MemoryLFUFreePercent
if lfuFreePercent <= 0 {
lfuFreePercent = 5
}
if capacityBytes > 0 {
if lfuFreePercent < 100 {
if usedPercent >= 100-lfuFreePercent {
startLFU = true
}
}
}
if startLFU {
var total, _ = this.list.Count()
if total > 0 {
@@ -476,33 +484,20 @@ func (this *MemoryStorage) purgeLoop() {
// 开始Flush任务
func (this *MemoryStorage) startFlush() {
var statCount = 0
var writeDelayMS float64 = 0
for key := range this.dirtyChan {
statCount++
if statCount == 100 {
statCount = 0
// delay some time to reduce load if needed
if !fsutils.DiskIsFast() {
loadStat, err := load.Avg()
if err == nil && loadStat != nil {
if loadStat.Load1 > 10 {
writeDelayMS = 100
} else if loadStat.Load1 > 5 {
writeDelayMS = 50
} else {
writeDelayMS = 0
}
}
}
}
this.flushItem(key)
if writeDelayMS > 0 {
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
if fsutils.IsInExtremelyHighLoad {
time.Sleep(1 * time.Second)
} else if fsutils.IsInHighLoad {
time.Sleep(100 * time.Millisecond)
}
}
}
@@ -518,9 +513,20 @@ func (this *MemoryStorage) flushItem(key string) {
item, ok := this.valuesMap[hash]
this.locker.RUnlock()
// 从内存中移除,并确保无论如何都会执行
defer func() {
_ = this.Delete(key)
// 重用内存,前提是确保内存不再被引用
if enableFragmentPool && ok && item.IsDone && !item.isReferring && len(item.BodyValue) > 0 {
SharedFragmentMemoryPool.Put(item.BodyValue)
}
}()
if !ok {
return
}
if !item.IsDone {
remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
return
@@ -529,6 +535,16 @@ func (this *MemoryStorage) flushItem(key string) {
return
}
// 检查是否在列表中防止未加入列表时就开始flush
isInList, err := this.list.Exist(types.String(hash))
if err != nil {
remotelogs.Error("CACHE", "flush items failed: "+err.Error())
return
}
if !isInList {
time.Sleep(1 * time.Second)
}
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
if err != nil {
if !CanIgnoreErr(err) {
@@ -566,26 +582,37 @@ func (this *MemoryStorage) flushItem(key string) {
HeaderSize: writer.HeaderSize(),
BodySize: writer.BodySize(),
})
// 从内存中移除
_ = this.Delete(key)
}
func (this *MemoryStorage) memoryCapacityBytes() int64 {
var maxSystemBytes = SharedManager.MaxSystemMemoryBytesPerStorage()
if this.policy == nil {
return 0
}
c1 := int64(0)
if this.policy.Capacity != nil {
c1 = this.policy.Capacity.Bytes()
return maxSystemBytes
}
if SharedManager.MaxMemoryCapacity != nil {
c2 := SharedManager.MaxMemoryCapacity.Bytes()
if c2 > 0 {
return c2
var capacityBytes = SharedManager.MaxMemoryCapacity.Bytes()
if capacityBytes > 0 {
if capacityBytes > maxSystemBytes {
return maxSystemBytes
}
return capacityBytes
}
}
return c1
var capacity = this.policy.Capacity // copy
if capacity != nil {
var capacityBytes = capacity.Bytes()
if capacityBytes > 0 {
if capacityBytes > maxSystemBytes {
return maxSystemBytes
}
return capacityBytes
}
}
return maxSystemBytes
}
func (this *MemoryStorage) deleteWithoutLocker(key string) error {

View File

@@ -3,6 +3,7 @@ package caches
import (
"bytes"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands"
"runtime"
@@ -271,6 +272,10 @@ func TestMemoryStorage_Purge(t *testing.T) {
}
func TestMemoryStorage_Expire(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
MemoryAutoPurgeInterval: 5,
}, nil)

View File

@@ -4,8 +4,8 @@ package caches
import "strings"
// 获取 ranges 文件路径
func partialRangesFilePath(path string) string {
// PartialRangesFilePath 获取 ranges 文件路径
func PartialRangesFilePath(path string) string {
// ranges路径
var dotIndex = strings.LastIndex(path, ".")
var rangePath string

View File

@@ -2,9 +2,9 @@ package caches
import (
"errors"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/cespare/xxhash"
"sync"
"time"
)
type MemoryWriter struct {
@@ -16,29 +16,51 @@ type MemoryWriter struct {
bodySize int64
status int
isDirty bool
maxSize int64
expectedBodySize int64
maxSize int64
hash uint64
item *MemoryItem
endFunc func()
endFunc func(valueItem *MemoryItem)
once sync.Once
}
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, maxSize int64, endFunc func()) *MemoryWriter {
w := &MemoryWriter{
storage: memoryStorage,
key: key,
expiredAt: expiredAt,
item: &MemoryItem{
ExpiresAt: expiredAt,
ModifiedAt: time.Now().Unix(),
Status: status,
},
status: status,
isDirty: isDirty,
maxSize: maxSize,
endFunc: endFunc,
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, expectedBodySize int64, maxSize int64, endFunc func(valueItem *MemoryItem)) *MemoryWriter {
var valueItem = &MemoryItem{
ExpiresAt: expiredAt,
ModifiedAt: fasttime.Now().Unix(),
Status: status,
}
if enableFragmentPool &&
expectedBodySize > 0 &&
expectedBodySize <= maxMemoryFragmentPoolItemSize {
bodyBytes, ok := SharedFragmentMemoryPool.Get(expectedBodySize) // try to reuse memory
if ok {
valueItem.BodyValue = bodyBytes
valueItem.IsPrepared = true
} else {
if expectedBodySize <= (16 << 20) {
var allocSize = (expectedBodySize/16384 + 1) * 16384
valueItem.BodyValue = make([]byte, allocSize)[:expectedBodySize]
valueItem.IsPrepared = true
SharedFragmentMemoryPool.IncreaseNew()
}
}
}
var w = &MemoryWriter{
storage: memoryStorage,
key: key,
expiredAt: expiredAt,
item: valueItem,
status: status,
isDirty: isDirty,
expectedBodySize: expectedBodySize,
maxSize: maxSize,
endFunc: endFunc,
}
w.hash = w.calculateHash(key)
return w
@@ -53,17 +75,32 @@ func (this *MemoryWriter) WriteHeader(data []byte) (n int, err error) {
// Write 写入数据
func (this *MemoryWriter) Write(data []byte) (n int, err error) {
this.bodySize += int64(len(data))
this.item.BodyValue = append(this.item.BodyValue, data...)
var l = len(data)
if l == 0 {
return
}
if this.item.IsPrepared {
if this.item.WriteOffset+int64(l) > this.expectedBodySize {
err = ErrWritingUnavailable
return
}
copy(this.item.BodyValue[this.item.WriteOffset:], data)
this.item.WriteOffset += int64(l)
} else {
this.item.BodyValue = append(this.item.BodyValue, data...)
}
this.bodySize += int64(l)
// 检查尺寸
if this.maxSize > 0 && this.bodySize > this.maxSize {
err = ErrEntityTooLarge
this.storage.IgnoreKey(this.key, this.maxSize)
return len(data), err
return l, err
}
return len(data), nil
return l, nil
}
// WriteAt 在指定位置写入数据
@@ -87,7 +124,7 @@ func (this *MemoryWriter) BodySize() int64 {
func (this *MemoryWriter) Close() error {
// 需要在Locker之外
defer this.once.Do(func() {
this.endFunc()
this.endFunc(this.item)
})
if this.item == nil {
@@ -96,30 +133,48 @@ func (this *MemoryWriter) Close() error {
this.storage.locker.Lock()
this.item.IsDone = true
this.storage.valuesMap[this.hash] = this.item
var err error
if this.isDirty {
if this.storage.parentStorage != nil {
this.storage.valuesMap[this.hash] = this.item
select {
case this.storage.dirtyChan <- this.key:
default:
// remove from values map
delete(this.storage.valuesMap, this.hash)
err = ErrWritingQueueFull
}
} else {
this.storage.valuesMap[this.hash] = this.item
}
} else {
this.storage.valuesMap[this.hash] = this.item
}
this.storage.locker.Unlock()
return nil
return err
}
// Discard 丢弃
func (this *MemoryWriter) Discard() error {
// 需要在Locker之外
defer this.once.Do(func() {
this.endFunc()
this.endFunc(this.item)
})
this.storage.locker.Lock()
delete(this.storage.valuesMap, this.hash)
if enableFragmentPool &&
this.item != nil &&
!this.item.isReferring &&
cap(this.item.BodyValue) >= minMemoryFragmentPoolItemSize {
SharedFragmentMemoryPool.Put(this.item.BodyValue)
}
this.storage.locker.Unlock()
return nil
}

View File

@@ -43,7 +43,7 @@ func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaH
isPartial: isPartial,
bodyOffset: bodyOffset,
ranges: ranges,
rangePath: partialRangesFilePath(rawWriter.Name()),
rangePath: PartialRangesFilePath(rawWriter.Name()),
metaHeaderSize: metaHeaderSize,
metaBodySize: metaBodySize,
}

View File

@@ -1,4 +1,5 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus || !linux
package compressions

View File

@@ -6,6 +6,7 @@ import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/compressions"
"io"
"os"
"testing"
)
@@ -49,3 +50,45 @@ func TestBrotliReader(t *testing.T) {
}
}
}
func BenchmarkBrotliReader(b *testing.B) {
data, err := os.ReadFile("./reader_brotli.go")
if err != nil {
b.Fatal(err)
}
var buf = bytes.NewBuffer([]byte{})
writer, err := compressions.NewBrotliWriter(buf, 5)
if err != nil {
b.Fatal(err)
}
_, err = writer.Write(data)
err = writer.Close()
if err != nil {
b.Fatal(err)
}
var compressedData = buf.Bytes()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
reader, readerErr := compressions.NewBrotliReader(bytes.NewBuffer(compressedData))
if readerErr != nil {
b.Fatal(readerErr)
}
var readBuf = make([]byte, 1024)
for {
_, readErr := reader.Read(readBuf)
if readErr != nil {
if readErr != io.EOF {
b.Fatal(readErr)
}
break
}
}
closeErr := reader.Close()
if closeErr != nil {
b.Fatal(closeErr)
}
}
})
}

View File

@@ -6,6 +6,7 @@ import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"io"
"net/http"
)
type ContentEncoding = string
@@ -58,3 +59,32 @@ func NewWriter(writer io.Writer, compressType serverconfigs.HTTPCompressionType,
}
return nil, errors.New("invalid compression type '" + compressType + "'")
}
// SupportEncoding 检查是否支持某个编码
func SupportEncoding(encoding string) bool {
return encoding == ContentEncodingBr ||
encoding == ContentEncodingGzip ||
encoding == ContentEncodingDeflate ||
encoding == ContentEncodingZSTD
}
// WrapHTTPResponse 包装http.Response对象
func WrapHTTPResponse(resp *http.Response) {
if resp == nil {
return
}
var contentEncoding = resp.Header.Get("Content-Encoding")
if len(contentEncoding) == 0 || !SupportEncoding(contentEncoding) {
return
}
reader, err := NewReader(resp.Body, contentEncoding)
if err != nil {
// unable to decode, we ignore the error
return
}
resp.Header.Del("Content-Encoding")
resp.Header.Del("Content-Length")
resp.Body = reader
}

View File

@@ -1,4 +1,5 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus || !linux
package compressions
@@ -27,7 +28,7 @@ func newBrotliWriter(writer io.Writer, level int) (*BrotliWriter, error) {
return &BrotliWriter{
writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{
Quality: level,
LGWin: 13, // TODO 在全局设置里可以设置此值
LGWin: 14, // TODO 在全局设置里可以设置此值
}),
level: level,
}, nil

View File

@@ -19,6 +19,10 @@ func NewZSTDWriter(writer io.Writer, level int) (Writer, error) {
}
func newZSTDWriter(writer io.Writer, level int) (Writer, error) {
if level < 0 {
level = 0
}
var zstdLevel = zstd.EncoderLevelFromZstd(level)
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel))

View File

@@ -9,6 +9,24 @@ import (
"testing"
)
func TestNewZSTDWriter_Level0(t *testing.T) {
var buf = &bytes.Buffer{}
writer, err := compressions.NewZSTDWriter(buf, 0)
if err != nil {
t.Fatal(err)
}
var originData = []byte(strings.Repeat("Hello", 1024))
_, err = writer.Write(originData)
if err != nil {
t.Fatal(err)
}
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log("origin data:", len(originData), "result:", buf.Len())
}
func TestNewZSTDWriter(t *testing.T) {
var buf = &bytes.Buffer{}
writer, err := compressions.NewZSTDWriter(buf, 10)

View File

@@ -12,8 +12,8 @@ const oldConfigFileName = "api.yaml"
type APIConfig struct {
OldRPC struct {
Endpoints []string `yaml:"endpoints" json:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
Endpoints []string `yaml:"endpoints,omitempty" json:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate,omitempty" json:"disableUpdate"`
} `yaml:"rpc,omitempty" json:"rpc"`
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
@@ -69,6 +69,7 @@ func LoadAPIConfig() (*APIConfig, error) {
// 自动生成新的配置文件
if filename == oldConfigFileName {
config.OldRPC.Endpoints = nil
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
}

View File

@@ -4,11 +4,16 @@ package configs_test
import (
"github.com/TeaOSLab/EdgeNode/internal/configs"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"gopkg.in/yaml.v3"
"testing"
)
func TestLoadClusterConfig(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
config, err := configs.LoadClusterConfig()
if err != nil {
t.Fatal(err)

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "1.2.8"
Version = "1.3.3"
ProductName = "Edge Node"
ProcessName = "edge-node"
@@ -14,5 +14,6 @@ const (
// SystemdServiceName systemd
SystemdServiceName = "edge-node"
AccessLogSockName = "edge-node.accesslog.sock"
AccessLogSockName = "edge-node.accesslog"
CacheGarbageSockName = "edge-node.cache.garbage"
)

View File

@@ -3,11 +3,16 @@ package iplibrary
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"testing"
)
func TestHTTPAPIAction_AddItem(t *testing.T) {
action := NewHTTPAPIAction()
if !testutils.IsSingleTesting() {
return
}
var action = NewHTTPAPIAction()
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
URL: "http://127.0.0.1:2345/post",
TimeoutSeconds: 0,
@@ -24,7 +29,11 @@ func TestHTTPAPIAction_AddItem(t *testing.T) {
}
func TestHTTPAPIAction_DeleteItem(t *testing.T) {
action := NewHTTPAPIAction()
if !testutils.IsSingleTesting() {
return
}
var action = NewHTTPAPIAction()
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
URL: "http://127.0.0.1:2345/post",
TimeoutSeconds: 0,

View File

@@ -4,13 +4,19 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/maps"
"testing"
"time"
)
func TestIPSetAction_Init(t *testing.T) {
action := iplibrary.NewIPSetAction()
_, lookupErr := executils.LookPath("iptables")
if lookupErr != nil {
return
}
var action = iplibrary.NewIPSetAction()
err := action.Init(&firewallconfigs.FirewallActionConfig{
Params: maps.Map{
"path": "/usr/bin/iptables",
@@ -25,6 +31,11 @@ func TestIPSetAction_Init(t *testing.T) {
}
func TestIPSetAction_AddItem(t *testing.T) {
_, lookupErr := executils.LookPath("iptables")
if lookupErr != nil {
return
}
var action = iplibrary.NewIPSetAction()
action.SetConfig(&firewallconfigs.FirewallActionIPSetConfig{
Path: "/usr/bin/iptables",
@@ -84,7 +95,12 @@ func TestIPSetAction_AddItem(t *testing.T) {
}
func TestIPSetAction_DeleteItem(t *testing.T) {
action := iplibrary.NewIPSetAction()
_, lookupErr := executils.LookPath("firewalld")
if lookupErr != nil {
return
}
var action = iplibrary.NewIPSetAction()
err := action.Init(&firewallconfigs.FirewallActionConfig{
Params: maps.Map{
"path": "/usr/bin/firewalld",

View File

@@ -3,12 +3,18 @@ package iplibrary
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"testing"
"time"
)
func TestIPTablesAction_AddItem(t *testing.T) {
action := NewIPTablesAction()
_, lookupErr := executils.LookPath("iptables")
if lookupErr != nil {
return
}
var action = NewIPTablesAction()
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
Path: "/usr/bin/iptables",
}
@@ -40,7 +46,12 @@ func TestIPTablesAction_AddItem(t *testing.T) {
}
func TestIPTablesAction_DeleteItem(t *testing.T) {
action := NewIPTablesAction()
_, lookupErr := executils.LookPath("firewalld")
if lookupErr != nil {
return
}
var action = NewIPTablesAction()
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
Path: "/usr/bin/firewalld",
}

View File

@@ -7,7 +7,7 @@ import (
)
func TestActionManager_UpdateActions(t *testing.T) {
manager := NewActionManager()
var manager = NewActionManager()
manager.UpdateActions([]*firewallconfigs.FirewallActionConfig{
{
Id: 1,

View File

@@ -3,11 +3,16 @@ package iplibrary
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"testing"
"time"
)
func TestScriptAction_AddItem(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
action := NewScriptAction()
action.config = &firewallconfigs.FirewallActionScriptConfig{
Path: "/tmp/ip-item.sh",
@@ -27,6 +32,10 @@ func TestScriptAction_AddItem(t *testing.T) {
}
func TestScriptAction_DeleteItem(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
action := NewScriptAction()
action.config = &firewallconfigs.FirewallActionScriptConfig{
Path: "/tmp/ip-item.sh",

View File

@@ -2,6 +2,7 @@ package iplibrary
import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert"
"runtime"
"testing"
@@ -75,8 +76,14 @@ func TestIPItem_Contains(t *testing.T) {
}
func TestIPItem_Memory(t *testing.T) {
var isSingleTest = testutils.IsSingleTesting()
var list = NewIPList()
for i := 0; i < 2_000_000; i ++ {
var count = 100
if isSingleTest {
count = 2_000_000
}
for i := 0; i < count; i++ {
list.Add(&IPItem{
Type: "ip",
Id: uint64(i),
@@ -87,7 +94,9 @@ func TestIPItem_Memory(t *testing.T) {
})
}
t.Log("waiting")
time.Sleep(10 * time.Second)
if isSingleTest {
time.Sleep(10 * time.Second)
}
}
func BenchmarkIPItem_Contains(b *testing.B) {
@@ -105,4 +114,3 @@ func BenchmarkIPItem_Contains(b *testing.B) {
}
}
}

View File

@@ -14,6 +14,8 @@ var GlobalWhiteIPList = NewIPList()
// IPList IP名单
// TODO IP名单可以分片关闭这样让每一片的数据量减少查询更快
type IPList struct {
isDeleted bool
itemsMap map[uint64]*IPItem // id => item
sortedItems []*IPItem
allItemsMap map[uint64]*IPItem // id => item
@@ -38,11 +40,19 @@ func NewIPList() *IPList {
}
func (this *IPList) Add(item *IPItem) {
if this.isDeleted {
return
}
this.addItem(item, true)
}
// AddDelay 延迟添加需要手工调用Sort()函数
func (this *IPList) AddDelay(item *IPItem) {
if this.isDeleted {
return
}
this.addItem(item, false)
}
@@ -60,6 +70,10 @@ func (this *IPList) Delete(itemId uint64) {
// Contains 判断是否包含某个IP
func (this *IPList) Contains(ip uint64) bool {
if this.isDeleted {
return false
}
this.locker.RLock()
if len(this.allItemsMap) > 0 {
this.locker.RUnlock()
@@ -75,6 +89,10 @@ func (this *IPList) Contains(ip uint64) bool {
// ContainsExpires 判断是否包含某个IP
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
if this.isDeleted {
return
}
this.locker.RLock()
if len(this.allItemsMap) > 0 {
this.locker.RUnlock()
@@ -94,6 +112,10 @@ func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
// ContainsIPStrings 是否包含一组IP中的任意一个并返回匹配的第一个Item
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
if this.isDeleted {
return
}
if len(ipStrings) == 0 {
return
}
@@ -125,6 +147,10 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
return
}
func (this *IPList) SetDeleted() {
this.isDeleted = true
}
func (this *IPList) addItem(item *IPItem, sortable bool) {
if item == nil {
return

View File

@@ -60,7 +60,7 @@ func (this *IPListDB) init() error {
var path = this.dir + "/ip_list.db"
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}

View File

@@ -16,16 +16,19 @@ func TestIPListDB_AddItem(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
err = db.AddItem(&pb.IPItem{
Id: 1,
IpFrom: "192.168.1.101",
IpTo: "",
Version: 1024,
ExpiredAt: time.Now().Unix(),
ExpiredAt: time.Now().Unix() + 3600,
Reason: "",
ListId: 2,
IsDeleted: true,
IsDeleted: false,
Type: "ipv4",
EventLevel: "error",
ListType: "black",
@@ -60,6 +63,9 @@ func TestIPListDB_ReadItems(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
defer func() {
_ = db.Close()
@@ -77,6 +83,9 @@ func TestIPListDB_ReadMaxVersion(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
t.Log(db.ReadMaxVersion())
}
@@ -85,6 +94,10 @@ func TestIPListDB_UpdateMaxVersion(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
err = db.UpdateMaxVersion(1027)
if err != nil {
t.Fatal(err)

View File

@@ -3,12 +3,17 @@
package iplibrary
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"testing"
"time"
)
func TestIPIsAllowed(t *testing.T) {
manager := NewIPListManager()
if !testutils.IsSingleTesting() {
return
}
var manager = NewIPListManager()
manager.init()
var before = time.Now()

View File

@@ -229,6 +229,11 @@ func (this *IPListManager) DeleteExpiredItems() {
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
var changedLists = map[*IPList]zero.Zero{}
for _, item := range items {
// 调试
if Tea.IsTesting() {
this.debugItem(item)
}
var list *IPList
// TODO 实现节点专有List
if item.ServerId > 0 { // 服务专有List
@@ -300,3 +305,12 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
}
}
}
// 调试IP信息
func (this *IPListManager) debugItem(item *pb.IPItem) {
if item.IsDeleted {
remotelogs.Debug("IP_ITEM_DEBUG", "delete '"+item.IpFrom+"'")
} else {
remotelogs.Debug("IP_ITEM_DEBUG", "add '"+item.IpFrom+"'")
}
}

View File

@@ -2,13 +2,18 @@ package iplibrary
import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/logs"
"testing"
"time"
)
func TestIPListManager_init(t *testing.T) {
manager := NewIPListManager()
if !testutils.IsSingleTesting() {
return
}
var manager = NewIPListManager()
manager.init()
t.Log(manager.listMap)
t.Log(SharedServerListManager.blackMap)
@@ -16,7 +21,11 @@ func TestIPListManager_init(t *testing.T) {
}
func TestIPListManager_check(t *testing.T) {
manager := NewIPListManager()
if !testutils.IsSingleTesting() {
return
}
var manager = NewIPListManager()
manager.init()
var before = time.Now()
@@ -28,7 +37,11 @@ func TestIPListManager_check(t *testing.T) {
}
func TestIPListManager_loop(t *testing.T) {
manager := NewIPListManager()
if !testutils.IsSingleTesting() {
return
}
var manager = NewIPListManager()
manager.Start()
err := manager.loop()
if err != nil {

View File

@@ -26,9 +26,9 @@ func init() {
type Manager struct {
isQuiting bool
tasks map[int64]*Task // itemId => *Task
categoryTasks map[string][]*Task // category => []*Task
locker sync.RWMutex
taskMap map[int64]*Task // itemId => *Task
categoryTaskMap map[string][]*Task // category => []*Task
locker sync.RWMutex
hasHTTPMetrics bool
hasTCPMetrics bool
@@ -37,8 +37,8 @@ type Manager struct {
func NewManager() *Manager {
return &Manager{
tasks: map[int64]*Task{},
categoryTasks: map[string][]*Task{},
taskMap: map[int64]*Task{},
categoryTaskMap: map[string][]*Task{},
}
}
@@ -56,7 +56,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
}
// 停用以前的 或 修改现在的
for itemId, task := range this.tasks {
for itemId, task := range this.taskMap {
newItem, ok := newMap[itemId]
if !ok || !newItem.IsOn { // 停用以前的
remotelogs.Println("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"'")
@@ -64,7 +64,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
if err != nil {
remotelogs.Error("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
}
delete(this.tasks, itemId)
delete(this.taskMap, itemId)
} else { // 更新已存在的
if newItem.Version != task.item.Version {
remotelogs.Println("METRIC_MANAGER", "update task '"+strconv.FormatInt(itemId, 10)+"'")
@@ -78,7 +78,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
if !newItem.IsOn {
continue
}
_, ok := this.tasks[newItem.Id]
_, ok := this.taskMap[newItem.Id]
if !ok {
remotelogs.Println("METRIC_MANAGER", "start task '"+strconv.FormatInt(newItem.Id, 10)+"'")
task := NewTask(newItem)
@@ -92,7 +92,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
remotelogs.Error("METRIC_MANAGER", "start task failed: "+err.Error())
continue
}
this.tasks[newItem.Id] = task
this.taskMap[newItem.Id] = task
}
}
@@ -100,11 +100,11 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
this.hasHTTPMetrics = false
this.hasTCPMetrics = false
this.hasUDPMetrics = false
this.categoryTasks = map[string][]*Task{}
for _, task := range this.tasks {
tasks := this.categoryTasks[task.item.Category]
this.categoryTaskMap = map[string][]*Task{}
for _, task := range this.taskMap {
var tasks = this.categoryTaskMap[task.item.Category]
tasks = append(tasks, task)
this.categoryTasks[task.item.Category] = tasks
this.categoryTaskMap[task.item.Category] = tasks
switch task.item.Category {
case serverconfigs.MetricItemCategoryHTTP:
@@ -124,10 +124,12 @@ func (this *Manager) Add(obj MetricInterface) {
}
this.locker.RLock()
for _, task := range this.categoryTasks[obj.MetricCategory()] {
var tasks = this.categoryTaskMap[obj.MetricCategory()]
this.locker.RUnlock()
for _, task := range tasks {
task.Add(obj)
}
this.locker.RUnlock()
}
func (this *Manager) HasHTTPMetrics() bool {
@@ -149,9 +151,9 @@ func (this *Manager) Quit() {
remotelogs.Println("METRIC_MANAGER", "quit")
this.locker.Lock()
for _, task := range this.tasks {
for _, task := range this.taskMap {
_ = task.Stop()
}
this.tasks = map[int64]*Task{}
this.taskMap = map[int64]*Task{}
this.locker.Unlock()
}

View File

@@ -11,7 +11,7 @@ func TestNewManager(t *testing.T) {
var manager = NewManager()
{
manager.Update([]*serverconfigs.MetricItemConfig{})
for _, task := range manager.tasks {
for _, task := range manager.taskMap {
t.Log(task.item.Id)
}
}
@@ -28,7 +28,7 @@ func TestNewManager(t *testing.T) {
Id: 3,
},
})
for _, task := range manager.tasks {
for _, task := range manager.taskMap {
t.Log("task:", task.item.Id)
}
}
@@ -43,7 +43,7 @@ func TestNewManager(t *testing.T) {
Id: 2,
},
})
for _, task := range manager.tasks {
for _, task := range manager.taskMap {
t.Log("task:", task.item.Id)
}
}
@@ -56,7 +56,7 @@ func TestNewManager(t *testing.T) {
Version: 1,
},
})
for _, task := range manager.tasks {
for _, task := range manager.taskMap {
t.Log("task:", task.item.Id)
}
}

View File

@@ -17,6 +17,6 @@ type Stat struct {
}
func SumStat(serverId int64, keys []string, time string, version int32, itemId int64) string {
keysData := strings.Join(keys, "$EDGE$")
var keysData = strings.Join(keys, "$EDGE$")
return strconv.FormatUint(fnv.HashString(strconv.FormatInt(serverId, 10)+"@"+keysData+"@"+time+"@"+strconv.Itoa(int(version))+"@"+strconv.FormatInt(itemId, 10)), 10)
}

View File

@@ -0,0 +1,20 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package metrics_test
import (
"github.com/TeaOSLab/EdgeNode/internal/metrics"
timeutil "github.com/iwind/TeaGo/utils/time"
"runtime"
"testing"
)
func BenchmarkSumStat(b *testing.B) {
runtime.GOMAXPROCS(2)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
metrics.SumStat(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1)
}
})
}

View File

@@ -19,6 +19,7 @@ import (
"os"
"strconv"
"sync"
"sync/atomic"
"time"
)
@@ -59,7 +60,7 @@ type Task struct {
serverIdMapLocker sync.Mutex
statsMap map[string]*Stat // 待写入队列hash => *Stat
statsLocker sync.Mutex
statsLocker sync.RWMutex
statsTicker *utils.Ticker
}
@@ -90,7 +91,7 @@ func (this *Task) Init() error {
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
@@ -237,7 +238,7 @@ func (this *Task) Add(obj MetricInterface) {
var keys = []string{}
for _, key := range this.item.Keys {
k := obj.MetricKey(key)
var k = obj.MetricKey(key)
// 忽略499状态
if key == "${status}" && k == "499" {
@@ -253,14 +254,19 @@ func (this *Task) Add(obj MetricInterface) {
}
var hash = SumStat(obj.MetricServerId(), keys, this.item.CurrentTime(), this.item.Version, this.item.Id)
this.statsLocker.Lock()
var countItems int
this.statsLocker.RLock()
oldStat, ok := this.statsMap[hash]
if !ok {
countItems = len(this.statsMap)
}
this.statsLocker.RUnlock()
if ok {
oldStat.Value += v
oldStat.Hash = hash
atomic.AddInt64(&oldStat.Value, 1)
} else {
// 防止过载
if len(this.statsMap) < MaxQueueSize {
if countItems < MaxQueueSize {
this.statsLocker.Lock()
this.statsMap[hash] = &Stat{
ServerId: obj.MetricServerId(),
Keys: keys,
@@ -268,9 +274,9 @@ func (this *Task) Add(obj MetricInterface) {
Time: this.item.CurrentTime(),
Hash: hash,
}
this.statsLocker.Unlock()
}
}
this.statsLocker.Unlock()
}
// Stop 停止任务

View File

@@ -4,11 +4,15 @@ package metrics_test
import (
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/metrics"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/rands"
"log"
"runtime"
"sync"
"testing"
"time"
)
@@ -213,3 +217,65 @@ func TestTask_Upload(t *testing.T) {
}
t.Log("ok")
}
var testingTask *metrics.Task
var testingTaskInitOnce = &sync.Once{}
func initTestingTask() {
testingTask = metrics.NewTask(&serverconfigs.MetricItemConfig{
Id: 1,
IsOn: false,
Category: "tcp",
Period: 1,
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
Keys: []string{"${remoteAddr}"},
Value: "${countRequest}",
})
err := testingTask.Init()
if err != nil {
log.Fatal(err)
}
err = testingTask.Start()
if err != nil {
log.Fatal(err)
}
}
func BenchmarkTask_Add(b *testing.B) {
runtime.GOMAXPROCS(1)
testingTaskInitOnce.Do(func() {
initTestingTask()
})
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
testingTask.Add(&taskRequest{})
}
})
}
type taskRequest struct {
}
func (this *taskRequest) MetricKey(key string) string {
return configutils.ParseVariables(key, func(varName string) (value string) {
return "1.2.3.4"
})
}
func (this *taskRequest) MetricValue(value string) (result int64, ok bool) {
return 1, true
}
func (this *taskRequest) MetricServerId() int64 {
return 1
}
func (this *taskRequest) MetricCategory() string {
return "http"
}

View File

@@ -5,6 +5,7 @@ package monitor
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs"
"google.golang.org/grpc/status"
@@ -12,6 +13,10 @@ import (
)
func TestValueQueue_RPC(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
t.Fatal(err)

View File

@@ -90,13 +90,13 @@ func (this *APIStream) loop() error {
break
}
message, err := nodeStream.Recv()
if err != nil {
message, streamErr := nodeStream.Recv()
if streamErr != nil {
if this.isQuiting {
remotelogs.Println("API_STREAM", "quit")
return nil
}
return err
return streamErr
}
// 处理消息

View File

@@ -1,8 +1,15 @@
package nodes
import "testing"
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"testing"
)
func TestAPIStream_Start(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
apiStream := NewAPIStream()
apiStream.Start()
}

View File

@@ -24,8 +24,6 @@ import (
"time"
)
var synFloodCounter = counters.NewCounter().WithGC()
// ClientConn 客户端连接
type ClientConn struct {
BaseClientConn
@@ -198,9 +196,9 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
var cost = time.Since(before).Seconds()
if cost > 1 {
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(float64(n)/cost), int64(n))
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.userPlanId, this.serverId, int64(float64(n)/cost), int64(n))
} else {
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(n), int64(n))
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.userPlanId, this.serverId, int64(n), int64(n))
}
}
}
@@ -292,13 +290,13 @@ func (this *ClientConn) LastErr() error {
}
func (this *ClientConn) resetSYNFlood() {
synFloodCounter.ResetKey("SYN_FLOOD:" + this.RawIP())
counters.SharedCounter.ResetKey("SYN_FLOOD:" + this.RawIP())
}
func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) {
var ip = this.RawIP()
if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
var result = synFloodCounter.IncreaseKey("SYN_FLOOD:"+ip, 60)
var result = counters.SharedCounter.IncreaseKey("SYN_FLOOD:"+ip, 60)
var minAttempts = synFloodConfig.MinAttempts
if minAttempts < 5 {
minAttempts = 5
@@ -307,7 +305,7 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
// 非TLS设置为两倍防止误封
minAttempts = 2 * minAttempts
}
if result >= types.Uint64(minAttempts) {
if result >= types.Uint32(minAttempts) {
var timeout = synFloodConfig.TimeoutSeconds
if timeout <= 0 {
timeout = 600

View File

@@ -16,6 +16,7 @@ type BaseClientConn struct {
isBound bool
userId int64
userPlanId int64
serverId int64
remoteAddr string
hasLimit bool
@@ -106,11 +107,31 @@ func (this *BaseClientConn) SetUserId(userId int64) {
}
}
func (this *BaseClientConn) SetUserPlanId(userPlanId int64) {
this.userPlanId = userPlanId
// 设置包装前连接
switch conn := this.rawConn.(type) {
case *tls.Conn:
nativeConn, ok := conn.NetConn().(ClientConnInterface)
if ok {
nativeConn.SetUserPlanId(userPlanId)
}
case *ClientConn:
conn.SetUserPlanId(userPlanId)
}
}
// UserId 获取当前连接所属服务的用户ID
func (this *BaseClientConn) UserId() int64 {
return this.userId
}
// UserPlanId 用户套餐ID
func (this *BaseClientConn) UserPlanId() int64 {
return this.userPlanId
}
// RawIP 原本IP
func (this *BaseClientConn) RawIP() string {
if len(this.rawIP) > 0 {

View File

@@ -18,9 +18,12 @@ type ClientConnInterface interface {
// SetServerId 设置服务ID
SetServerId(serverId int64) (goNext bool)
// SetUserId 设置所属服务的用户ID
// SetUserId 设置所属网站的用户ID
SetUserId(userId int64)
// SetUserPlanId 设置
SetUserPlanId(userPlanId int64)
// UserId 获取当前连接所属服务的用户ID
UserId() int64

View File

@@ -110,6 +110,10 @@ func TestHTTPAccessLogQueue_Push2(t *testing.T) {
}
func TestHTTPAccessLogQueue_Memory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
testutils.StartMemoryStats(t)
debug.SetGCPercent(10)

View File

@@ -3,13 +3,14 @@ package nodes
import (
"context"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"runtime"
"testing"
"time"
)
func TestHTTPClientPool_Client(t *testing.T) {
pool := NewHTTPClientPool()
var pool = NewHTTPClientPool()
{
var origin = &serverconfigs.OriginConfig{
@@ -54,7 +55,10 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
for i := 0; i < 10; i++ {
t.Log("get", i)
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
time.Sleep(1 * time.Second)
if testutils.IsSingleTesting() {
time.Sleep(1 * time.Second)
}
}
}

View File

@@ -38,15 +38,16 @@ type HTTPRequest struct {
requestId string
// 外部参数
RawReq *http.Request
RawWriter http.ResponseWriter
ReqServer *serverconfigs.ServerConfig
ReqHost string // 请求的Host
ServerName string // 实际匹配到的Host
ServerAddr string // 实际启动的服务器监听地址
IsHTTP bool
IsHTTPS bool
IsHTTP3 bool
RawReq *http.Request
RawWriter http.ResponseWriter
ReqServer *serverconfigs.ServerConfig
ReqHost string // 请求的Host
ServerName string // 实际匹配到的Host
ServerAddr string // 实际启动的服务器监听地址
IsHTTP bool
IsHTTPS bool
IsHTTP3 bool
isHealthCheck bool
// 共享参数
nodeConfig *nodeconfigs.NodeConfig
@@ -84,6 +85,8 @@ type HTTPRequest struct {
isAttack bool // 是否是攻击请求
requestBodyData []byte // 读取的Body内容
isWebsocketResponse bool // 是否为Websocket响应非请求
// WAF相关
firewallPolicyId int64
firewallRuleGroupId int64
@@ -99,6 +102,8 @@ type HTTPRequest struct {
disableLog bool // 是否在当前请求中关闭Log
forceLog bool // 是否强制记录日志
isHijacked bool
// script相关操作
isDone bool
}
@@ -168,10 +173,9 @@ func (this *HTTPRequest) Do() {
}
// 处理健康检查
var isHealthCheck = false
var healthCheckKey = this.RawReq.Header.Get(serverconfigs.HealthCheckHeaderName)
if len(healthCheckKey) > 0 {
if this.doHealthCheck(healthCheckKey, &isHealthCheck) {
if this.doHealthCheck(healthCheckKey, &this.isHealthCheck) {
this.doEnd()
return
}
@@ -191,45 +195,33 @@ func (this *HTTPRequest) Do() {
}
// 套餐
if this.ReqServer.UserPlan != nil && !this.ReqServer.UserPlan.IsAvailable() {
this.doPlanExpires()
this.doEnd()
return
}
// 流量限制
if this.ReqServer.TrafficLimit != nil && this.ReqServer.TrafficLimit.IsOn && !this.ReqServer.TrafficLimit.IsEmpty() && this.ReqServer.TrafficLimitStatus != nil && this.ReqServer.TrafficLimitStatus.IsValid() {
this.doTrafficLimit()
this.doEnd()
return
}
// UAM
if !isHealthCheck {
if this.web.UAM != nil {
if this.web.UAM.IsOn {
if this.doUAM() {
this.doEnd()
return
}
}
} else if this.ReqServer.UAM != nil && this.ReqServer.UAM.IsOn {
this.web.UAM = this.ReqServer.UAM
if this.doUAM() {
this.doEnd()
return
}
if this.ReqServer.UserPlan != nil {
if this.doPlanBefore() {
this.doEnd()
return
}
}
// CC
if !isHealthCheck {
if this.web.CC != nil {
if this.web.CC.IsOn {
if this.doCC() {
this.doEnd()
return
}
// 流量限制
if this.ReqServer.TrafficLimitStatus != nil && this.ReqServer.TrafficLimitStatus.IsValid() {
if this.doTrafficLimit(this.ReqServer.TrafficLimitStatus) {
this.doEnd()
return
}
}
// UAM
var uamIsCalled = false
if !this.isHealthCheck {
if this.web.UAM == nil && this.ReqServer.UAM != nil && this.ReqServer.UAM.IsOn {
this.web.UAM = this.ReqServer.UAM
}
if this.web.UAM != nil && this.web.UAM.IsOn && this.isUAMRequest() {
uamIsCalled = true
if this.doUAM() {
this.doEnd()
return
}
}
}
@@ -242,6 +234,28 @@ func (this *HTTPRequest) Do() {
}
}
// UAM
if !this.isHealthCheck && !uamIsCalled {
if this.web.UAM != nil && this.web.UAM.IsOn {
if this.doUAM() {
this.doEnd()
return
}
}
}
// CC
if !this.isHealthCheck {
if this.web.CC != nil {
if this.web.CC.IsOn {
if this.doCC() {
this.doEnd()
return
}
}
}
}
// 防盗链
if !this.isSubRequest && this.web.Referers != nil && this.web.Referers.IsOn {
if this.doCheckReferers() {
@@ -278,6 +292,16 @@ func (this *HTTPRequest) Do() {
if this.web.Compression != nil && this.web.Compression.IsOn && this.web.Compression.Level > 0 {
this.writer.SetCompression(this.web.Compression)
}
// HLS
if this.web.HLS != nil &&
this.web.HLS.Encrypting != nil &&
this.web.HLS.Encrypting.IsOn {
if this.processHLSBefore() {
this.doEnd()
return
}
}
}
// 开始调用
@@ -388,7 +412,7 @@ func (this *HTTPRequest) doEnd() {
// 流量统计
// TODO 增加是否开启开关
if this.ReqServer != nil && this.ReqServer.Id > 0 {
if this.ReqServer != nil && this.ReqServer.Id > 0 && !this.isHealthCheck /** 健康检查时不统计 **/ {
var totalBytes int64 = 0
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
@@ -410,6 +434,8 @@ func (this *HTTPRequest) doEnd() {
var countAttacks int64 = 0
var attackBytes int64 = 0
var countWebsocketConnections int64 = 0
if this.isCached {
countCached = 1
cachedBytes = totalBytes
@@ -421,8 +447,11 @@ func (this *HTTPRequest) doEnd() {
attackBytes = totalBytes
}
}
if this.isWebsocketResponse {
countWebsocketConnections = 1
}
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, totalBytes, cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, totalBytes, cachedBytes, 1, countCached, countAttacks, attackBytes, countWebsocketConnections, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
// 指标
if metrics.SharedManager.HasHTTPMetrics() {
@@ -624,6 +653,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
this.web.CC = web.CC
}
// HLS
if web.HLS != nil && (web.HLS.IsPrior || isTop) {
this.web.HLS = web.HLS
}
// 重写规则
if len(web.RewriteRefs) > 0 {
for index, ref := range web.RewriteRefs {
@@ -854,6 +888,24 @@ func (this *HTTPRequest) Format(source string) string {
return this.requestHeadersString()
case "serverName":
return this.ServerName
case "serverAddr":
var nodeConfig = this.nodeConfig
if nodeConfig != nil && nodeConfig.GlobalServerConfig != nil && nodeConfig.GlobalServerConfig.HTTPAll.EnableServerAddrVariable {
if len(this.requestRemoteAddrs()) > 1 {
return "" // hidden for security
}
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn != nil {
conn, ok := requestConn.(net.Conn)
if ok {
host, _, _ := net.SplitHostPort(conn.LocalAddr().String())
if len(host) > 0 {
return host
}
}
}
}
return ""
case "serverPort":
return strconv.Itoa(this.requestServerPort())
case "hostname":
@@ -1153,10 +1205,32 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
this.web.RemoteAddr != nil &&
this.web.RemoteAddr.IsOn &&
!this.web.RemoteAddr.IsEmpty() {
var remoteAddr = this.Format(this.web.RemoteAddr.Value)
if net.ParseIP(remoteAddr) != nil {
this.remoteAddr = remoteAddr
return remoteAddr
if this.web.RemoteAddr.HasValues() { // multiple values
for _, value := range this.web.RemoteAddr.Values() {
var remoteAddr = this.Format(value)
if len(remoteAddr) > 0 && net.ParseIP(remoteAddr) != nil {
this.remoteAddr = remoteAddr
return remoteAddr
}
}
} else { // single value
var remoteAddr = this.Format(this.web.RemoteAddr.Value)
if len(remoteAddr) > 0 && net.ParseIP(remoteAddr) != nil {
this.remoteAddr = remoteAddr
return remoteAddr
}
}
// 如果是从Header中读取则直接返回原始IP
if this.web.RemoteAddr.Type == serverconfigs.HTTPRemoteAddrTypeRequestHeader {
var remoteAddr = this.RawReq.RemoteAddr
host, _, err := net.SplitHostPort(remoteAddr)
if err == nil {
this.remoteAddr = host
return host
} else {
return remoteAddr
}
}
}
@@ -1222,7 +1296,7 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For")
if len(forwardedFor) > 0 {
commaIndex := strings.Index(forwardedFor, ",")
if commaIndex > 0 {
if commaIndex > 0 && !lists.ContainsString(result, forwardedFor[:commaIndex]) {
result = append(result, forwardedFor[:commaIndex])
}
}
@@ -1230,7 +1304,7 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
// Real-IP
{
realIP, ok := this.RawReq.Header["X-Real-IP"]
if ok && len(realIP) > 0 {
if ok && len(realIP) > 0 && !lists.ContainsString(result, realIP[0]) {
result = append(result, realIP[0])
}
}
@@ -1238,7 +1312,7 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
// Real-Ip
{
realIP, ok := this.RawReq.Header["X-Real-Ip"]
if ok && len(realIP) > 0 {
if ok && len(realIP) > 0 && !lists.ContainsString(result, realIP[0]) {
result = append(result, realIP[0])
}
}
@@ -1248,7 +1322,9 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
var remoteAddr = this.RawReq.RemoteAddr
host, _, err := net.SplitHostPort(remoteAddr)
if err == nil {
result = append(result, host)
if !lists.ContainsString(result, host) {
result = append(result, host)
}
} else {
result = append(result, remoteAddr)
}
@@ -1381,11 +1457,25 @@ func (this *HTTPRequest) requestScheme() string {
// 请求的服务器地址中的端口
func (this *HTTPRequest) requestServerPort() int {
_, port, err := net.SplitHostPort(this.ServerAddr)
if err == nil {
return types.Int(port)
if len(this.ServerAddr) > 0 {
_, port, err := net.SplitHostPort(this.ServerAddr)
if err == nil && len(port) > 0 {
return types.Int(port)
}
}
return 0
var host = this.RawReq.Host
if len(host) > 0 {
_, port, err := net.SplitHostPort(host)
if err == nil && len(port) > 0 {
return types.Int(port)
}
}
if this.IsHTTP {
return 80
}
return 443
}
func (this *HTTPRequest) Id() string {
@@ -1565,10 +1655,10 @@ func (this *HTTPRequest) setForwardHeaders(header http.Header) {
this.RawReq.Header.Set("Connection", "keep-alive")
}
remoteAddr := this.RawReq.RemoteAddr
host, _, err := net.SplitHostPort(remoteAddr)
var rawRemoteAddr = this.RawReq.RemoteAddr
host, _, err := net.SplitHostPort(rawRemoteAddr)
if err == nil {
remoteAddr = host
rawRemoteAddr = host
}
// x-real-ip
@@ -1576,24 +1666,24 @@ func (this *HTTPRequest) setForwardHeaders(header http.Header) {
_, ok1 := header["X-Real-IP"]
_, ok2 := header["X-Real-Ip"]
if !ok1 && !ok2 {
header["X-Real-IP"] = []string{remoteAddr}
header["X-Real-IP"] = []string{this.requestRemoteAddr(true)}
}
}
// X-Forwarded-For
if this.reverseProxy != nil && this.reverseProxy.ShouldAddXForwardedForHeader() {
forwardedFor, ok := header["X-Forwarded-For"]
if ok {
if ok && len(forwardedFor) > 0 { // already exists
_, hasForwardHeader := this.RawReq.Header["X-Forwarded-For"]
if hasForwardHeader {
header["X-Forwarded-For"] = []string{strings.Join(forwardedFor, ", ") + ", " + remoteAddr}
header["X-Forwarded-For"] = []string{strings.Join(forwardedFor, ", ") + ", " + rawRemoteAddr}
}
} else {
var clientRemoteAddr = this.requestRemoteAddr(true)
if len(clientRemoteAddr) > 0 && clientRemoteAddr != remoteAddr {
header["X-Forwarded-For"] = []string{clientRemoteAddr + ", " + remoteAddr}
if len(clientRemoteAddr) > 0 && clientRemoteAddr != rawRemoteAddr {
header["X-Forwarded-For"] = []string{clientRemoteAddr + ", " + rawRemoteAddr}
} else {
header["X-Forwarded-For"] = []string{remoteAddr}
header["X-Forwarded-For"] = []string{rawRemoteAddr}
}
}
}

View File

@@ -43,7 +43,7 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
if uriChanged {
this.uri = newURI
}
this.tags = append(this.tags, ref.AuthPolicy.Type)
this.tags = append(this.tags, "auth:"+ref.AuthPolicy.Type)
return
} else {
// Basic Auth比较特殊
@@ -64,7 +64,7 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
}
}
this.writer.WriteHeader(http.StatusUnauthorized)
this.tags = append(this.tags, ref.AuthPolicy.Type)
this.tags = append(this.tags, "auth:"+ref.AuthPolicy.Type)
return true
}
}

View File

@@ -3,6 +3,7 @@ package nodes
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/compressions"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -130,7 +131,22 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var tags = []string{}
// 检查是否有缓存
var key = this.Format(this.cacheRef.Key)
var key string
if this.web.Cache.Key != nil && this.web.Cache.Key.IsOn && len(this.web.Cache.Key.Host) > 0 {
key = configutils.ParseVariables(this.cacheRef.Key, func(varName string) (value string) {
switch varName {
case "scheme":
return this.web.Cache.Key.Scheme
case "host":
return this.web.Cache.Key.Host
default:
return this.Format("${" + varName + "}")
}
})
} else {
key = this.Format(this.cacheRef.Key)
}
if len(key) == 0 {
this.cacheRef = nil
cacheBypassDescription = "BYPASS, empty key"
@@ -274,7 +290,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
}
if err != nil {
if err == caches.ErrNotFound {
if errors.Is(err, caches.ErrNotFound) {
// 移除请求中的 If-None-Match 和 If-Modified-Since防止源站返回304而无法缓存
if this.reverseProxy != nil {
this.RawReq.Header.Del("If-None-Match")
this.RawReq.Header.Del("If-Modified-Since")
}
// cache相关变量
this.varMapping["cache.status"] = "MISS"
@@ -365,24 +387,24 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
}
// ETag
// 这里强制设置ETag如果先前源站设置了ETag将会被覆盖避免因为源站的ETag导致源站返回304 Not Modified
var respHeader = this.writer.Header()
var eTag = ""
var eTag = respHeader.Get("ETag")
var lastModifiedAt = reader.LastModified()
if lastModifiedAt > 0 {
if len(tags) > 0 {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "_" + strings.Join(tags, "_") + "\""
} else {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\""
}
respHeader.Del("Etag")
if !isPartialCache {
respHeader["ETag"] = []string{eTag}
if len(eTag) == 0 {
if lastModifiedAt > 0 {
if len(tags) > 0 {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "_" + strings.Join(tags, "_") + "\""
} else {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\""
}
respHeader.Del("Etag")
if !isPartialCache {
respHeader["ETag"] = []string{eTag}
}
}
}
// 支持 Last-Modified
// 这里强制设置Last-Modified如果先前源站设置了Last-Modified将会被覆盖避免因为源站的Last-Modified导致源站返回304 Not Modified
var modifiedTime = ""
if lastModifiedAt > 0 {
modifiedTime = time.Unix(utils.GMTUnixTime(lastModifiedAt), 0).Format("Mon, 02 Jan 2006 15:04:05") + " GMT"
@@ -490,7 +512,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
if err != nil {
this.varMapping["cache.status"] = "MISS"
if err == caches.ErrInvalidRange {
if errors.Is(err, caches.ErrInvalidRange) {
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true

View File

@@ -9,16 +9,20 @@ import (
)
const httpStatusPageTemplate = `<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>${status} ${statusMessage}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
address { line-height: 1.8; }
</style>
</head>
<body>
<h1>${status} ${statusMessage}</h1>
<p>${message}</p>
<address>Connection: ${remoteAddr} (Client) -&gt; ${serverAddr} (Server)</address>
<address>Request ID: ${requestId}.</address>
</body>
@@ -39,8 +43,6 @@ func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage s
return types.String(statusCode)
case "statusMessage":
return http.StatusText(statusCode)
case "requestId":
return this.requestId
case "message":
var acceptLanguages = this.RawReq.Header.Get("Accept-Language")
if len(acceptLanguages) > 0 {
@@ -54,7 +56,7 @@ func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage s
}
return enMessage
}
return "${" + varName + "}"
return this.Format("${" + varName + "}")
})
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
@@ -107,7 +109,7 @@ func (this *HTTPRequest) write50x(err error, statusCode int, enMessage string, z
}
return "The site is unavailable now, cause: " + enMessage + "."
}
return "${" + varName + "}"
return this.Format("${" + varName + "}")
})
this.ProcessResponseHeaders(this.writer.Header(), statusCode)

View File

@@ -0,0 +1,16 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import "net/http"
func (this *HTTPRequest) processHLSBefore() (blocked bool) {
// stub
return false
}
func (this *HTTPRequest) processM3u8Response(resp *http.Response) error {
// stub
return nil
}

View File

@@ -25,6 +25,13 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
continue
}
if len(u.ExceptDomains) > 0 && configutils.MatchDomains(u.ExceptDomains, this.ReqHost) {
continue
}
if len(u.OnlyDomains) > 0 && !configutils.MatchDomains(u.OnlyDomains, this.ReqHost) {
continue
}
var status = u.Status
if status <= 0 {
if searchEngineRegex.MatchString(this.RawReq.UserAgent()) {
@@ -55,7 +62,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
httpRedirect(this.writer, this.RawReq, afterURL, status)
return true
}
} else if u.MatchRegexp { // 正则匹配
@@ -97,7 +104,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
httpRedirect(this.writer, this.RawReq, afterURL, status)
return true
} else { // 精准匹配
if fullURL == u.RealBeforeURL() {
@@ -120,7 +127,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
httpRedirect(this.writer, this.RawReq, afterURL, status)
return true
}
}
@@ -139,11 +146,6 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
}
// 如果跳转前后域名一致,则终止
if u.DomainAfter == reqHost {
return false
}
var scheme = u.DomainAfterScheme
if len(scheme) == 0 {
scheme = this.requestScheme()
@@ -155,6 +157,11 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
return false
}
// 如果跳转前后域名一致,则终止
if u.DomainAfter == reqHost {
return false
}
this.ProcessResponseHeaders(this.writer.Header(), status)
// 参数
@@ -163,7 +170,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
afterURL += this.uri[qIndex:]
}
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
httpRedirect(this.writer, this.RawReq, afterURL, status)
return true
}
} else if u.Type == serverconfigs.HTTPHostRedirectTypePort {
@@ -212,7 +219,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
}
this.ProcessResponseHeaders(this.writer.Header(), status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
httpRedirect(this.writer, this.RawReq, afterURL, status)
return true
}
}

View File

@@ -20,6 +20,6 @@ func (this *HTTPRequest) checkLnRequest() bool {
return false
}
func (this *HTTPRequest) getLnOrigin(excludingNodeIds []int64) (originConfig *serverconfigs.OriginConfig, lnNodeId int64, hasMultipleNodes bool) {
func (this *HTTPRequest) getLnOrigin(excludingNodeIds []int64, urlHash uint64) (originConfig *serverconfigs.OriginConfig, lnNodeId int64, hasMultipleNodes bool) {
return nil, 0, false
}

View File

@@ -7,9 +7,9 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/types"
"net"
"net/http"
"time"
)
@@ -34,58 +34,63 @@ func (this *HTTPRequest) doMismatch() {
}
// 根据配置进行相应的处理
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
var statusCode = 404
var httpAllConfig = globalServerConfig.HTTPAll
var mismatchAction = httpAllConfig.DomainMismatchAction
var nodeConfig = sharedNodeConfig // copy
if nodeConfig != nil {
var globalServerConfig = nodeConfig.GlobalServerConfig
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
var statusCode = 404
var httpAllConfig = globalServerConfig.HTTPAll
var mismatchAction = httpAllConfig.DomainMismatchAction
if mismatchAction != nil && mismatchAction.Options != nil {
var mismatchStatusCode = mismatchAction.Options.GetInt("statusCode")
if mismatchStatusCode > 0 && mismatchStatusCode >= 100 && mismatchStatusCode < 1000 {
statusCode = mismatchStatusCode
}
}
// 是否正在访问IP
if globalServerConfig.HTTPAll.NodeIPShowPage && net.ParseIP(this.ReqHost) != nil {
var contentHTML = this.Format(globalServerConfig.HTTPAll.NodeIPPageHTML)
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
this.writer.WriteHeader(statusCode)
_, _ = this.writer.WriteString(contentHTML)
return
}
// 检查cc
// TODO 可以在管理端配置是否开启以及最多尝试次数
// 要考虑到服务在切换集群时,域名未生效状态时,用户访问的仍然是老集群中的节点,就会产生找不到域名的情况
if len(remoteIP) > 0 {
const maxAttempts = 100
if ttlcache.SharedCache.IncreaseInt64("MISMATCH_DOMAIN:"+remoteIP, int64(1), time.Now().Unix()+60, false) > maxAttempts {
// 在加入之前再次检查黑名单
if !waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP) {
waf.SharedIPBlackList.Add(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP, time.Now().Unix()+3600)
if mismatchAction != nil && mismatchAction.Options != nil {
var mismatchStatusCode = mismatchAction.Options.GetInt("statusCode")
if mismatchStatusCode > 0 && mismatchStatusCode >= 100 && mismatchStatusCode < 1000 {
statusCode = mismatchStatusCode
}
}
}
// 处理当前连接
if mismatchAction != nil && mismatchAction.Code == "page" {
if mismatchAction.Options != nil {
var contentHTML = this.Format(mismatchAction.Options.GetString("contentHTML"))
// 是否正在访问IP
if globalServerConfig.HTTPAll.NodeIPShowPage && utils.IsWildIP(this.ReqHost) {
this.writer.statusCode = statusCode
var contentHTML = this.Format(globalServerConfig.HTTPAll.NodeIPPageHTML)
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(contentHTML))
_, _ = this.writer.WriteString(contentHTML)
return
}
// 检查cc
// TODO 可以在管理端配置是否开启以及最多尝试次数
// 要考虑到服务在切换集群时,域名未生效状态时,用户访问的仍然是老集群中的节点,就会产生找不到域名的情况
if len(remoteIP) > 0 {
const maxAttempts = 100
if ttlcache.SharedInt64Cache.IncreaseInt64("MISMATCH_DOMAIN:"+remoteIP, int64(1), time.Now().Unix()+60, false) > maxAttempts {
// 在加入之前再次检查黑名单
if !waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP) {
waf.SharedIPBlackList.Add(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteIP, time.Now().Unix()+3600)
}
}
}
// 处理当前连接
if mismatchAction != nil && mismatchAction.Code == serverconfigs.DomainMismatchActionPage {
if mismatchAction.Options != nil {
this.writer.statusCode = statusCode
var contentHTML = this.Format(mismatchAction.Options.GetString("contentHTML"))
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(contentHTML))
} else {
http.Error(this.writer, "404 page not found: '"+this.URL()+"'", http.StatusNotFound)
}
return
} else {
http.Error(this.writer, "404 page not found: '"+this.URL()+"'", http.StatusNotFound)
this.Close()
return
}
return
} else {
http.Error(this.writer, "404 page not found: '"+this.URL()+"'", http.StatusNotFound)
this.Close()
return
}
}

View File

@@ -2,7 +2,6 @@ package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea"
@@ -46,9 +45,13 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
}
func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, status int) (shouldStop bool) {
var url = this.URL()
for _, page := range pages {
if !page.MatchURL(url) {
continue
}
if page.Match(status) {
if len(page.BodyType) == 0 || page.BodyType == shared.BodyTypeURL {
if len(page.BodyType) == 0 || page.BodyType == serverconfigs.HTTPPageBodyTypeURL {
if urlSchemeRegexp.MatchString(page.URL) {
var newStatus = page.NewStatus
if newStatus <= 0 {
@@ -115,7 +118,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
}
return true
} else if page.BodyType == shared.BodyTypeHTML {
} else if page.BodyType == serverconfigs.HTTPPageBodyTypeHTML {
// 这里需要实现设置Status因为在Format()中可以获取${status}等变量
if page.NewStatus > 0 {
this.writer.statusCode = page.NewStatus
@@ -147,6 +150,18 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
this.writer.SetOk()
}
return true
} else if page.BodyType == serverconfigs.HTTPPageBodyTypeRedirectURL {
var newURL = this.Format(page.URL)
if len(newURL) == 0 {
newURL = "/"
}
if page.NewStatus > 0 && httpStatusIsRedirect(page.NewStatus) {
httpRedirect(this.writer, this.RawReq, newURL, page.NewStatus)
} else {
httpRedirect(this.writer, this.RawReq, newURL, http.StatusTemporaryRedirect)
}
this.writer.SetOk()
return true
}
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package nodes
// 检查套餐
func (this *HTTPRequest) doPlanBefore() (blocked bool) {
// stub
return false
}

View File

@@ -1,19 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"net/http"
)
// 套餐过期
func (this *HTTPRequest) doPlanExpires() {
this.tags = append(this.tags, "plan")
var statusCode = http.StatusNotFound
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
this.writer.WriteHeader(statusCode)
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))
}

View File

@@ -43,7 +43,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
var newURL = "https://" + host + this.RawReq.RequestURI
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
httpRedirect(this.writer, this.RawReq, newURL, statusCode)
return true
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
@@ -25,9 +27,10 @@ func (this *HTTPRequest) doReverseProxy() {
var failedOriginIds []int64
var failedLnNodeIds []int64
var failStatusCode int
for i := 0; i < retries; i++ {
originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1)
originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1, &failStatusCode)
if !shouldRetry {
break
}
@@ -41,7 +44,7 @@ func (this *HTTPRequest) doReverseProxy() {
}
// 请求源站
func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool) (originId int64, lnNodeId int64, shouldRetry bool) {
func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool, failStatusCode *int) (originId int64, lnNodeId int64, shouldRetry bool) {
// 对URL的处理
var stripPrefix = this.reverseProxy.StripPrefix
var requestURI = this.reverseProxy.RequestURI
@@ -65,7 +68,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// 二级节点
var hasMultipleLnNodes = false
if this.cacheRef != nil || (this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && this.nodeConfig.GlobalServerConfig.HTTPAll.ForceLnRequest) {
origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds)
origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds, fnv.HashString(this.URL()))
if origin != nil {
// 强制变更原来访问的域名
requestHost = this.ReqHost
@@ -89,6 +92,10 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
}
if origin == nil {
origin = this.reverseProxy.NextOrigin(requestCall)
if origin != nil && origin.Id > 0 && (*failStatusCode >= 403 && *failStatusCode <= 404) && lists.ContainsInt64(failedOriginIds, origin.Id) {
this.writeCode(*failStatusCode, "", "")
return
}
}
requestCall.CallResponseCallbacks(this.writer)
if origin == nil {
@@ -306,7 +313,8 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
if requestErr != nil {
// 客户端取消请求,则不提示
httpErr, ok := requestErr.(*url.Error)
var httpErr *url.Error
ok := errors.As(requestErr, &httpErr)
if !ok {
if isHTTPOrigin {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
@@ -320,7 +328,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
}
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+requestErr.Error())
} else if httpErr.Err != context.Canceled {
} else if !errors.Is(httpErr, context.Canceled) {
if isHTTPOrigin {
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling()
@@ -332,7 +340,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
shouldRetry = true
this.uri = oldURI // 恢复备份
if httpErr.Err != io.EOF {
if httpErr.Err != io.EOF && !errors.Is(httpErr.Err, http.ErrBodyReadAfterClose) {
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
}
@@ -346,14 +354,14 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
} else {
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
}
if httpErr.Err != io.EOF {
if httpErr.Err != io.EOF && !errors.Is(httpErr.Err, http.ErrBodyReadAfterClose) {
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
}
} else {
// 是否为客户端方面的错误
var isClientError = false
if ok {
if httpErr.Err == context.Canceled {
if errors.Is(httpErr, context.Canceled) {
// 如果是服务器端主动关闭,则无需提示
if this.isConnClosed() {
this.disableLog = true
@@ -373,6 +381,43 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
return
}
if resp == nil {
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
return
}
// fix Content-Type
if resp.Header["Content-Type"] == nil {
resp.Header["Content-Type"] = []string{}
}
// 40x && 50x
*failStatusCode = resp.StatusCode
if ((resp.StatusCode >= 500 && resp.StatusCode < 510 && this.reverseProxy.Retry50X) ||
(resp.StatusCode >= 403 && resp.StatusCode <= 404 && this.reverseProxy.Retry40X)) &&
(originId > 0 || (lnNodeId > 0 && hasMultipleLnNodes)) &&
!isLastRetry {
if resp.Body != nil {
_ = resp.Body.Close()
}
shouldRetry = true
return
}
// 尝试从缓存中恢复
if resp.StatusCode >= 500 && // support 50X only
resp.StatusCode < 510 &&
this.cacheCanTryStale &&
this.web.Cache.Stale != nil &&
this.web.Cache.Stale.IsOn &&
(len(this.web.Cache.Stale.Status) == 0 || lists.ContainsInt(this.web.Cache.Stale.Status, resp.StatusCode)) {
var ok = this.doCacheRead(true)
if ok {
return
}
}
// 记录相关数据
this.originStatus = int32(resp.StatusCode)
@@ -397,21 +442,43 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
// Page optimization
if this.web.Optimization != nil && resp.Body != nil && this.cacheRef != nil /** must under cache **/ {
err := this.web.Optimization.FilterResponse(resp)
err := this.web.Optimization.FilterResponse(this.URL(), resp)
if err != nil {
this.write50x(err, http.StatusBadGateway, "Page Optimization: Fail to read content from origin", "内容优化:从源站读取内容失败", false)
this.write50x(err, http.StatusBadGateway, "Page Optimization: fail to read content from origin", "内容优化:从源站读取内容失败", false)
return
}
}
// HLS
if this.web.HLS != nil &&
this.web.HLS.Encrypting != nil &&
this.web.HLS.Encrypting.IsOn &&
resp.StatusCode == http.StatusOK {
m3u8Err := this.processM3u8Response(resp)
if m3u8Err != nil {
this.write50x(m3u8Err, http.StatusBadGateway, "m3u8 encrypt: fail to read content from origin", "m3u8加密从源站读取内容失败", false)
return
}
}
// 设置Charset
// TODO 这里应该可以设置文本类型的列表,以及是否强制覆盖所有文本类型的字符集
// TODO 这里应该可以设置文本类型的列表
if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
contentTypes, ok := resp.Header["Content-Type"]
if ok && len(contentTypes) > 0 {
var contentType = contentTypes[0]
if this.web.Charset.Force {
var semiIndex = strings.Index(contentType, ";")
if semiIndex > 0 {
contentType = contentType[:semiIndex]
}
}
if _, found := textMimeMap[contentType]; found {
resp.Header["Content-Type"][0] = contentType + "; charset=" + this.web.Charset.Charset
var newCharset = this.web.Charset.Charset
if this.web.Charset.IsUpper {
newCharset = strings.ToUpper(newCharset)
}
resp.Header["Content-Type"][0] = contentType + "; charset=" + newCharset
}
}
}

View File

@@ -31,10 +31,10 @@ func (this *HTTPRequest) doRewrite() (shouldShop bool) {
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
if this.rewriteRule.RedirectStatus > 0 {
this.ProcessResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
httpRedirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
} else {
this.ProcessResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
httpRedirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
}
return true
}

View File

@@ -1,7 +1,7 @@
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea"
@@ -18,7 +18,7 @@ func (this *HTTPRequest) doShutdown() {
return
}
if len(shutdown.BodyType) == 0 || shutdown.BodyType == shared.BodyTypeURL {
if len(shutdown.BodyType) == 0 || shutdown.BodyType == serverconfigs.HTTPPageBodyTypeURL {
// URL
if urlSchemeRegexp.MatchString(shutdown.URL) {
this.doURL(http.MethodGet, shutdown.URL, "", shutdown.Status, true)
@@ -80,7 +80,7 @@ func (this *HTTPRequest) doShutdown() {
} else {
this.writer.SetOk()
}
} else if shutdown.BodyType == shared.BodyTypeHTML {
} else if shutdown.BodyType == serverconfigs.HTTPPageBodyTypeHTML {
// 自定义响应Headers
if shutdown.Status > 0 {
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
@@ -98,5 +98,17 @@ func (this *HTTPRequest) doShutdown() {
} else {
this.writer.SetOk()
}
} else if shutdown.BodyType == serverconfigs.HTTPPageBodyTypeRedirectURL {
var newURL = shutdown.URL
if len(newURL) == 0 {
newURL = "/"
}
if shutdown.Status > 0 && httpStatusIsRedirect(shutdown.Status) {
httpRedirect(this.writer, this.RawReq, newURL, shutdown.Status)
} else {
httpRedirect(this.writer, this.RawReq, newURL, http.StatusTemporaryRedirect)
}
this.writer.SetOk()
}
}

View File

@@ -3,6 +3,7 @@ package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/assert"
"net/http"
"runtime"
"testing"
)
@@ -10,26 +11,45 @@ import (
func TestHTTPRequest_RedirectToHTTPS(t *testing.T) {
var a = assert.NewAssertion(t)
{
req := &HTTPRequest{
rawReq, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatal(err)
}
var req = &HTTPRequest{
RawReq: rawReq,
RawWriter: NewEmptyResponseWriter(nil),
ReqServer: &serverconfigs.ServerConfig{
IsOn: true,
Web: &serverconfigs.HTTPWebConfig{
IsOn: true,
RedirectToHttps: &serverconfigs.HTTPRedirectToHTTPSConfig{},
},
},
}
req.init()
req.Do()
a.IsBool(req.web.RedirectToHttps.IsOn == false)
}
{
req := &HTTPRequest{
rawReq, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatal(err)
}
var req = &HTTPRequest{
RawReq: rawReq,
RawWriter: NewEmptyResponseWriter(nil),
ReqServer: &serverconfigs.ServerConfig{
IsOn: true,
Web: &serverconfigs.HTTPWebConfig{
IsOn: true,
RedirectToHttps: &serverconfigs.HTTPRedirectToHTTPSConfig{
IsOn: true,
},
},
},
}
req.init()
req.Do()
a.IsBool(req.web.RedirectToHttps.IsOn == true)
}

View File

@@ -7,18 +7,41 @@ import (
)
// 流量限制
func (this *HTTPRequest) doTrafficLimit() {
var config = this.ReqServer.TrafficLimit
func (this *HTTPRequest) doTrafficLimit(status *serverconfigs.TrafficLimitStatus) (blocked bool) {
if status == nil {
return false
}
this.tags = append(this.tags, "bandwidth")
// 如果是网站单独设置的流量限制,则检查是否已关闭
var config = this.ReqServer.TrafficLimit
if (config == nil || !config.IsOn) && status.PlanId == 0 {
return false
}
// 如果是套餐设置的流量限制,即使套餐变更了(变更套餐或者变更套餐的限制),仍然会提示流量超限
this.tags = append(this.tags, "trafficLimit")
var statusCode = 509
this.writer.statusCode = statusCode
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.WriteHeader(statusCode)
if len(config.NoticePageBody) != 0 {
// check plan traffic limit
if (config == nil || !config.IsOn) && this.ReqServer.PlanId() > 0 && this.nodeConfig != nil {
var planConfig = this.nodeConfig.FindPlan(this.ReqServer.PlanId())
if planConfig != nil && planConfig.TrafficLimit != nil && planConfig.TrafficLimit.IsOn {
config = planConfig.TrafficLimit
}
}
if config != nil && len(config.NoticePageBody) != 0 {
_, _ = this.writer.WriteString(this.Format(config.NoticePageBody))
} else {
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultTrafficLimitNoticePageBody))
}
return true
}

View File

@@ -4,7 +4,13 @@
package nodes
// UAM
func (this *HTTPRequest) doUAM() (block bool) {
func (this *HTTPRequest) isUAMRequest() bool {
// stub
return false
}
// UAM
func (this *HTTPRequest) doUAM() (block bool) {
// stub
return false
}

View File

@@ -67,8 +67,8 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
// 当前服务的独立设置
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
blocked, breakChecking := this.checkWAFRequest(this.web.FirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying, false)
if blocked {
blockedRequest, breakChecking := this.checkWAFRequest(this.web.FirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying, false)
if blockedRequest {
return true
}
if breakChecking {
@@ -78,8 +78,8 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
// 公用的防火墙设置
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.IsOn {
blocked, breakChecking := this.checkWAFRequest(this.ReqServer.HTTPFirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying, this.web.FirewallRef.IgnoreGlobalRules)
if blocked {
blockedRequest, breakChecking := this.checkWAFRequest(this.ReqServer.HTTPFirewallPolicy, forceLog, forceLogRequestBody, forceLogRegionDenying, this.web.FirewallRef.IgnoreGlobalRules)
if blockedRequest {
return true
}
if breakChecking {
@@ -96,6 +96,8 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
return
}
var isDefendMode = firewallPolicy.Mode == firewallconfigs.FirewallModeDefend
// 检查IP白名单
var remoteAddrs []string
if len(this.remoteAddr) > 0 {
@@ -122,7 +124,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
}
// 检查IP黑名单
if firewallPolicy.Mode == firewallconfigs.FirewallModeDefend {
if isDefendMode {
for _, ref := range inbound.AllDenyListRefs() {
if ref.IsOn && ref.ListId > 0 {
list := iplibrary.SharedIPListManager.FindList(ref.ListId)
@@ -161,19 +163,20 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
}
// 检查地区封禁
if firewallPolicy.Mode == firewallconfigs.FirewallModeDefend {
if firewallPolicy.Inbound.Region != nil && firewallPolicy.Inbound.Region.IsOn {
var regionConfig = firewallPolicy.Inbound.Region
if regionConfig.IsNotEmpty() {
for _, remoteAddr := range remoteAddrs {
var result = iplib.LookupIP(remoteAddr)
if result != nil && result.IsOk() {
var currentURL = this.URL()
if regionConfig.MatchCountryURL(currentURL) {
// 检查国家/地区级别封禁
if !regionConfig.IsAllowedCountry(result.CountryId(), result.ProvinceId()) {
this.firewallPolicyId = firewallPolicy.Id
if firewallPolicy.Inbound.Region != nil && firewallPolicy.Inbound.Region.IsOn {
var regionConfig = firewallPolicy.Inbound.Region
if regionConfig.IsNotEmpty() {
for _, remoteAddr := range remoteAddrs {
var result = iplib.LookupIP(remoteAddr)
if result != nil && result.IsOk() {
var currentURL = this.URL()
if regionConfig.MatchCountryURL(currentURL) {
// 检查国家/地区级别封禁
if !regionConfig.IsAllowedCountry(result.CountryId(), result.ProvinceId()) {
this.firewallPolicyId = firewallPolicy.Id
if isDefendMode {
var promptHTML string
if len(regionConfig.CountryHTML) > 0 {
promptHTML = regionConfig.CountryHTML
@@ -193,23 +196,27 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
// 延时返回,避免攻击
time.Sleep(1 * time.Second)
}
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyCountry")
}
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyCountry")
}
if isDefendMode {
return true, false
}
}
}
if regionConfig.MatchProvinceURL(currentURL) {
// 检查省份封禁
if !regionConfig.IsAllowedProvince(result.CountryId(), result.ProvinceId()) {
this.firewallPolicyId = firewallPolicy.Id
if regionConfig.MatchProvinceURL(currentURL) {
// 检查省份封禁
if !regionConfig.IsAllowedProvince(result.CountryId(), result.ProvinceId()) {
this.firewallPolicyId = firewallPolicy.Id
if isDefendMode {
var promptHTML string
if len(regionConfig.ProvinceHTML) > 0 {
promptHTML = regionConfig.ProvinceHTML
@@ -229,14 +236,16 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
// 延时返回,避免攻击
time.Sleep(1 * time.Second)
}
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyProvince")
}
// 停止日志
if !logDenying {
this.disableLog = true
} else {
this.tags = append(this.tags, "denyProvince")
}
if isDefendMode {
return true, false
}
}
@@ -257,8 +266,11 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
return
}
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
result, err := w.MatchRequest(this, this.writer, this.web.FirewallRef.DefaultCaptchaType)
if result.IsAllowed && (len(result.AllowScope) == 0 || result.AllowScope == waf.AllowScopeGlobal) {
breakChecking = true
}
if forceLog && logRequestBody && result.HasRequestBody && result.Set != nil && result.Set.HasAttackActions() {
this.wafHasRequestBody = true
}
if err != nil {
@@ -268,28 +280,28 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
return
}
if ruleSet != nil {
if result.Set != nil {
if forceLog {
this.forceLog = true
}
if ruleSet.HasSpecialActions() {
if result.Set.HasSpecialActions() {
this.firewallPolicyId = firewallPolicy.Id
this.firewallRuleGroupId = types.Int64(ruleGroup.Id)
this.firewallRuleSetId = types.Int64(ruleSet.Id)
this.firewallRuleGroupId = types.Int64(result.Group.Id)
this.firewallRuleSetId = types.Int64(result.Set.Id)
if ruleSet.HasAttackActions() {
if result.Set.HasAttackActions() {
this.isAttack = true
}
// 添加统计
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.ReqServer.Id, this.firewallRuleGroupId, ruleSet.Actions)
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.ReqServer.Id, this.firewallRuleGroupId, result.Set.Actions)
}
this.firewallActions = append(ruleSet.ActionCodes(), firewallPolicy.Mode)
this.firewallActions = append(result.Set.ActionCodes(), firewallPolicy.Mode)
}
return !goNext, false
return !result.GoNext, breakChecking
}
// call response waf
@@ -307,23 +319,26 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
}
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
blocked := this.checkWAFResponse(this.web.FirewallPolicy, resp, forceLog, forceLogRequestBody, false)
if blocked {
blockedRequest, breakChecking := this.checkWAFResponse(this.web.FirewallPolicy, resp, forceLog, forceLogRequestBody, false)
if blockedRequest {
return true
}
if breakChecking {
return
}
}
// 公用的防火墙设置
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.IsOn {
blocked := this.checkWAFResponse(this.ReqServer.HTTPFirewallPolicy, resp, forceLog, forceLogRequestBody, this.web.FirewallRef.IgnoreGlobalRules)
if blocked {
blockedRequest, _ := this.checkWAFResponse(this.ReqServer.HTTPFirewallPolicy, resp, forceLog, forceLogRequestBody, this.web.FirewallRef.IgnoreGlobalRules)
if blockedRequest {
return true
}
}
return
}
func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, resp *http.Response, forceLog bool, logRequestBody bool, ignoreRules bool) (blocked bool) {
func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, resp *http.Response, forceLog bool, logRequestBody bool, ignoreRules bool) (blocked bool, breakChecking bool) {
if firewallPolicy == nil || !firewallPolicy.IsOn || !firewallPolicy.Outbound.IsOn || firewallPolicy.Mode == firewallconfigs.FirewallModeBypass {
return
}
@@ -338,8 +353,11 @@ func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFi
return
}
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer)
if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
result, err := w.MatchResponse(this, resp, this.writer)
if result.IsAllowed && (len(result.AllowScope) == 0 || result.AllowScope == waf.AllowScopeGlobal) {
breakChecking = true
}
if forceLog && logRequestBody && result.HasRequestBody && result.Set != nil && result.Set.HasAttackActions() {
this.wafHasRequestBody = true
}
if err != nil {
@@ -349,28 +367,28 @@ func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFi
return
}
if ruleSet != nil {
if result.Set != nil {
if forceLog {
this.forceLog = true
}
if ruleSet.HasSpecialActions() {
if result.Set.HasSpecialActions() {
this.firewallPolicyId = firewallPolicy.Id
this.firewallRuleGroupId = types.Int64(ruleGroup.Id)
this.firewallRuleSetId = types.Int64(ruleSet.Id)
this.firewallRuleGroupId = types.Int64(result.Group.Id)
this.firewallRuleSetId = types.Int64(result.Set.Id)
if ruleSet.HasAttackActions() {
if result.Set.HasAttackActions() {
this.isAttack = true
}
// 添加统计
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.ReqServer.Id, this.firewallRuleGroupId, ruleSet.Actions)
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.ReqServer.Id, this.firewallRuleGroupId, result.Set.Actions)
}
this.firewallActions = append(ruleSet.ActionCodes(), firewallPolicy.Mode)
this.firewallActions = append(result.Set.ActionCodes(), firewallPolicy.Mode)
}
return !goNext
return !result.GoNext, breakChecking
}
// WAFRaw 原始请求
@@ -469,3 +487,10 @@ func (this *HTTPRequest) WAFMaxRequestSize() int64 {
func (this *HTTPRequest) DisableAccessLog() {
this.disableLog = true
}
// DisableStat 停用统计
func (this *HTTPRequest) DisableStat() {
if this.web != nil {
this.web.StatRef = nil
}
}

View File

@@ -61,6 +61,9 @@ func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shou
}
}
// 标记
this.isWebsocketResponse = true
// 设置指定的来源域
if !this.web.Websocket.RequestSameOrigin && len(this.web.Websocket.RequestOrigin) > 0 {
var newRequestOrigin = this.web.Websocket.RequestOrigin
@@ -77,7 +80,6 @@ func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shou
}
// 连接源站
// TODO 增加N次错误重试重试的时候需要尝试不同的源站
originConn, _, err := OriginConnect(this.origin, this.requestServerPort(), this.RawReq.RemoteAddr, requestHost)
if err != nil {
if isLastRetry {

View File

@@ -11,14 +11,13 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/compressions"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/readers"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/utils/writers"
_ "github.com/biessek/golang-ico"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gowebp"
_ "golang.org/x/image/bmp"
@@ -33,21 +32,19 @@ import (
"net/textproto"
"os"
"path/filepath"
"runtime"
"strings"
"sync/atomic"
)
var webpMaxBufferSize int64 = 1_000_000_000
var webpTotalBufferSize int64 = 0
var webPThreads int32
var webPMaxThreads int32 = 1
var webPIgnoreURLSet = setutils.NewFixedSet(131072)
func init() {
if !teaconst.IsMain {
return
}
var systemMemory = utils.SystemMemoryGB() / 8
if systemMemory > 0 {
webpMaxBufferSize = int64(systemMemory) * 1024 * 1024 * 1024
webPMaxThreads = int32(runtime.NumCPU() / 4)
if webPMaxThreads < 1 {
webPMaxThreads = 1
}
}
@@ -78,6 +75,7 @@ type HTTPWriter struct {
// WebP
webpIsEncoding bool
webpOriginContentType string
webpQuality int
// Compression
compressionConfig *serverconfigs.HTTPCompressionConfig
@@ -341,7 +339,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), this.calculateHeaderLength(), totalSize, cacheRef.MaxSizeBytes(), this.isPartial)
if err != nil {
if err == caches.ErrEntityTooLarge && addStatusHeader {
if errors.Is(err, caches.ErrEntityTooLarge) && addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, entity too large")
}
@@ -481,8 +479,8 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
contentTypeWritten = true
}
err := cacheWriter.WriteAt(start, data)
if err != nil {
writeErr := cacheWriter.WriteAt(start, data)
if writeErr != nil {
hasError = true
this.cacheIsFinished = false
}
@@ -495,7 +493,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
return
}
var cacheReader = readers.NewTeeReaderCloser(resp.Body, this.cacheWriter)
var cacheReader = readers.NewTeeReaderCloser(resp.Body, this.cacheWriter, false)
resp.Body = cacheReader
this.rawReader = cacheReader
@@ -529,6 +527,7 @@ func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) {
if policy.RequireCache && this.req.cacheRef == nil {
return
}
this.webpQuality = policy.Quality
// 限制最小和最大尺寸
// TODO 需要将reader修改为LimitReader
@@ -547,30 +546,35 @@ func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) {
this.req.web.WebP.IsOn &&
this.req.web.WebP.MatchResponse(contentType, size, filepath.Ext(this.req.Path()), this.req.Format) &&
this.req.web.WebP.MatchAccept(this.req.requestHeader("Accept")) {
// 检查是否已经因为尺寸过大而忽略
if webPIgnoreURLSet.Has(this.req.URL()) {
return
}
// 如果已经是WebP不再重复处理
// TODO 考虑是否需要很严格的匹配
if strings.Contains(contentType, "image/webp") {
return
}
// 检查内存
if atomic.LoadInt64(&webpTotalBufferSize) >= webpMaxBufferSize {
// 检查当前是否正在转换
if atomic.LoadInt32(&webPThreads) >= webPMaxThreads {
return
}
var contentEncoding = this.GetHeader("Content-Encoding")
switch contentEncoding {
case "gzip", "deflate", "br", "zstd":
reader, err := compressions.NewReader(resp.Body, contentEncoding)
if err != nil {
if len(contentEncoding) > 0 {
if compressions.SupportEncoding(contentEncoding) {
reader, err := compressions.NewReader(resp.Body, contentEncoding)
if err != nil {
return
}
this.Header().Del("Content-Encoding")
this.Header().Del("Content-Length")
this.rawReader = reader
} else {
return
}
this.Header().Del("Content-Encoding")
this.Header().Del("Content-Length")
this.rawReader = reader
case "": // 空
default:
return
}
this.webpOriginContentType = contentType
@@ -598,7 +602,7 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
var contentEncoding = this.GetHeader("Content-Encoding")
if this.compressionConfig == nil || !this.compressionConfig.IsOn {
if lists.ContainsString([]string{"gzip", "deflate", "br", "zstd"}, contentEncoding) && !httpAcceptEncoding(acceptEncodings, contentEncoding) {
if compressions.SupportEncoding(contentEncoding) && !httpAcceptEncoding(acceptEncodings, contentEncoding) {
reader, err := compressions.NewReader(resp.Body, contentEncoding)
if err != nil {
return
@@ -615,12 +619,12 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
return
}
if this.compressionConfig.Level <= 0 {
if this.compressionConfig.Level < 0 {
return
}
// 如果已经有编码则不处理
if len(contentEncoding) > 0 && (!this.compressionConfig.DecompressData || !lists.ContainsString([]string{"gzip", "deflate", "br", "zstd"}, contentEncoding)) {
if len(contentEncoding) > 0 && (!this.compressionConfig.DecompressData || !compressions.SupportEncoding(contentEncoding)) {
return
}
@@ -787,12 +791,10 @@ func (this *HTTPWriter) AddHeaders(header http.Header) {
continue
}
switch key {
case "ETag":
case "Accept-CH", "ETag", "Content-MD5", "IM", "P3P", "WWW-Authenticate", "X-Request-ID":
newHeaders[key] = value
default:
for _, v := range value {
newHeaders.Add(key, v)
}
newHeaders[http.CanonicalHeaderKey(key)] = value
}
}
}
@@ -804,6 +806,8 @@ func (this *HTTPWriter) Write(data []byte) (n int, err error) {
}
n, err = this.writer.Write(data)
this.checkPlanBandwidth(n)
return
}
@@ -908,7 +912,7 @@ func (this *HTTPWriter) SendResp(resp *http.Response) (int64, error) {
// Redirect 跳转
func (this *HTTPWriter) Redirect(status int, url string) {
http.Redirect(this.rawWriter, this.req.RawReq, url, status)
httpRedirect(this, this.req.RawReq, url, status)
this.isFinished = true
}
@@ -964,6 +968,7 @@ func (this *HTTPWriter) Close() {
func (this *HTTPWriter) Hijack() (conn net.Conn, buf *bufio.ReadWriter, err error) {
hijack, ok := this.rawWriter.(http.Hijacker)
if ok {
this.req.isHijacked = true
return hijack.Hijack()
}
return
@@ -984,7 +989,7 @@ func (this *HTTPWriter) DelayRead() bool {
// 计算stale时长
func (this *HTTPWriter) calculateStaleLife() int {
var staleLife = 600 // TODO 可以在缓存策略里设置此时间
var staleLife = caches.DefaultStaleCacheSeconds
var staleConfig = this.req.web.Cache.Stale
if staleConfig != nil && staleConfig.IsOn {
// 从Header中读取stale-if-error
@@ -1015,6 +1020,11 @@ func (this *HTTPWriter) calculateStaleLife() int {
func (this *HTTPWriter) finishWebP() {
// 处理WebP
if this.webpIsEncoding {
atomic.AddInt32(&webPThreads, 1)
defer func() {
atomic.AddInt32(&webPThreads, -1)
}()
var webpCacheWriter caches.Writer
// 准备WebP Cache
@@ -1074,24 +1084,38 @@ func (this *HTTPWriter) finishWebP() {
var err error
if isGif {
gifImage, err = gif.DecodeAll(reader)
if gifImage != nil && (gifImage.Config.Width > gowebp.WebPMaxDimension || gifImage.Config.Height > gowebp.WebPMaxDimension) {
webPIgnoreURLSet.Push(this.req.URL())
return
}
} else {
imageData, _, err = image.Decode(reader)
if imageData != nil {
var bound = imageData.Bounds()
if bound.Max.X > gowebp.WebPMaxDimension || bound.Max.Y > gowebp.WebPMaxDimension {
webPIgnoreURLSet.Push(this.req.URL())
return
}
}
}
if err != nil {
// 发生了错误终止处理
webPIgnoreURLSet.Push(this.req.URL())
return
}
var totalBytes = reader.TotalBytes()
atomic.AddInt64(&webpTotalBufferSize, totalBytes)
defer func() {
atomic.AddInt64(&webpTotalBufferSize, -totalBytes)
}()
var f = types.Float32(this.req.web.WebP.Quality)
if f > 100 {
f = 100
var f = types.Float32(this.webpQuality)
if f <= 0 || f > 100 {
if this.size > (8<<20) || this.size <= 0 {
f = 30
} else if this.size > (1 << 20) {
f = 50
} else if this.size > (128 << 10) {
f = 60
} else {
f = 75
}
}
if imageData != nil {
@@ -1267,7 +1291,7 @@ func (this *HTTPWriter) calculateHeaderLength() (result int) {
func (this *HTTPWriter) shouldIgnoreHeader(name string) bool {
switch name {
case "Set-Cookie", "Strict-Transport-Security", "Alt-Svc", "Upgrade":
case "Set-Cookie", "Strict-Transport-Security", "Alt-Svc", "Upgrade", "X-Cache":
return true
default:
return (this.isPartial && name == "Content-Range")

View File

@@ -11,3 +11,7 @@ import (
func (this *HTTPWriter) canSendfile() (*os.File, bool) {
return nil, false
}
func (this *HTTPWriter) checkPlanBandwidth(n int) {
// stub
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/types"
"net"
)
@@ -45,7 +46,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
}
}
tlsPolicy, _, err := this.matchSSL(this.helloServerName(clientInfo))
tlsPolicy, _, err := this.matchSSL(this.helloServerNames(clientInfo))
if err != nil {
return nil, err
}
@@ -68,7 +69,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
}
}
tlsPolicy, cert, err := this.matchSSL(this.helloServerName(clientInfo))
tlsPolicy, cert, err := this.matchSSL(this.helloServerNames(clientInfo))
if err != nil {
return nil, err
}
@@ -84,7 +85,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
}
// 根据域名匹配证书
func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.Certificate, error) {
func (this *BaseListener) matchSSL(domains []string) (*sslconfigs.SSLPolicy, *tls.Certificate, error) {
var group = this.Group
if group == nil {
@@ -98,7 +99,7 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
// 如果域名为空,则取第一个
// 通常域名为空是因为是直接通过IP访问的
if len(domain) == 0 {
if len(domains) == 0 {
if group.IsHTTPS() && globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
return nil, nil, errors.New("no tls server name matched")
}
@@ -115,9 +116,25 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
}
return nil, nil, errors.New("no tls server name found")
}
var firstDomain = domains[0]
// 通过网站域名配置匹配
server, _ := this.findNamedServer(domain)
var server *serverconfigs.ServerConfig
var matchedDomain string
for _, domain := range domains {
server, _ = this.findNamedServer(domain, true)
if server != nil {
matchedDomain = domain
break
}
}
if server == nil {
server, _ = this.findNamedServer(firstDomain, false)
if server != nil {
matchedDomain = firstDomain
}
}
if server == nil {
// 找不到或者此时的服务没有配置证书需要搜索所有的Server通过SSL证书内容中的DNSName匹配
// 此功能仅为了兼容以往版本v1.0.4),不应该作为常态启用
@@ -126,14 +143,14 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
if searchingServer.SSLPolicy() == nil || !searchingServer.SSLPolicy().IsOn {
continue
}
cert, ok := searchingServer.SSLPolicy().MatchDomain(domain)
cert, ok := searchingServer.SSLPolicy().MatchDomain(firstDomain)
if ok {
return searchingServer.SSLPolicy(), cert, nil
}
}
}
return nil, nil, errors.New("no server found for '" + domain + "'")
return nil, nil, errors.New("no server found for '" + firstDomain + "'")
}
if server.SSLPolicy() == nil || !server.SSLPolicy().IsOn {
// 找不到或者此时的服务没有配置证书需要搜索所有的Server通过SSL证书内容中的DNSName匹配
@@ -143,32 +160,32 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
if searchingServer.SSLPolicy() == nil || !searchingServer.SSLPolicy().IsOn {
continue
}
cert, ok := searchingServer.SSLPolicy().MatchDomain(domain)
cert, ok := searchingServer.SSLPolicy().MatchDomain(matchedDomain)
if ok {
return searchingServer.SSLPolicy(), cert, nil
}
}
}
return nil, nil, errors.New("no cert found for '" + domain + "'")
return nil, nil, errors.New("no cert found for '" + matchedDomain + "'")
}
// 证书是否匹配
var sslConfig = server.SSLPolicy()
cert, ok := sslConfig.MatchDomain(domain)
cert, ok := sslConfig.MatchDomain(matchedDomain)
if ok {
return sslConfig, cert, nil
}
if len(sslConfig.Certs) == 0 {
remotelogs.ServerError(server.Id, "BASE_LISTENER", "no ssl certs found for '"+domain+"', server id: "+types.String(server.Id), "", nil)
remotelogs.ServerError(server.Id, "BASE_LISTENER", "no ssl certs found for '"+matchedDomain+"', server id: "+types.String(server.Id), "", nil)
}
return sslConfig, sslConfig.FirstCert(), nil
}
// 根据域名来查找匹配的域名
func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconfigs.ServerConfig, serverName string) {
func (this *BaseListener) findNamedServer(name string, exactly bool) (serverConfig *serverconfigs.ServerConfig, serverName string) {
serverConfig, serverName = this.findNamedServerMatched(name)
if serverConfig != nil {
return
@@ -179,10 +196,10 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
if globalServerConfig != nil &&
len(globalServerConfig.HTTPAll.DefaultDomain) > 0 &&
(!matchDomainStrictly || configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) || (globalServerConfig.HTTPAll.AllowNodeIP && net.ParseIP(name) != nil)) {
(!matchDomainStrictly || configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) || (globalServerConfig.HTTPAll.AllowNodeIP && utils.IsWildIP(name))) {
if globalServerConfig.HTTPAll.AllowNodeIP &&
globalServerConfig.HTTPAll.NodeIPShowPage &&
net.ParseIP(name) != nil {
utils.IsWildIP(name) {
return
} else {
var defaultDomain = globalServerConfig.HTTPAll.DefaultDomain
@@ -193,18 +210,22 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
}
}
if matchDomainStrictly && !configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!globalServerConfig.HTTPAll.AllowNodeIP || net.ParseIP(name) == nil) {
if matchDomainStrictly && !configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!globalServerConfig.HTTPAll.AllowNodeIP || (!utils.IsWildIP(name) || globalServerConfig.HTTPAll.NodeIPShowPage)) {
return
}
// 如果没有找到,则匹配到第一个
var group = this.Group
var currentServers = group.Servers()
var countServers = len(currentServers)
if countServers == 0 {
return nil, ""
if !exactly {
// 如果没有找到,则匹配到第一个
var group = this.Group
var currentServers = group.Servers()
var countServers = len(currentServers)
if countServers == 0 {
return nil, ""
}
return currentServers[0], name
}
return currentServers[0], name
return
}
// 严格查找域名
@@ -233,16 +254,23 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
}
// 从Hello信息中获取服务名称
func (this *BaseListener) helloServerName(clientInfo *tls.ClientHelloInfo) string {
var serverName = clientInfo.ServerName
if len(serverName) == 0 && clientInfo.Conn != nil {
func (this *BaseListener) helloServerNames(clientInfo *tls.ClientHelloInfo) (serverNames []string) {
if len(clientInfo.ServerName) != 0 {
serverNames = append(serverNames, clientInfo.ServerName)
return
}
if clientInfo.Conn != nil {
var localAddr = clientInfo.Conn.LocalAddr()
if localAddr != nil {
tcpAddr, ok := localAddr.(*net.TCPAddr)
if ok {
serverName = tcpAddr.IP.String()
serverNames = append(serverNames, tcpAddr.IP.String())
}
}
}
return serverName
serverNames = append(serverNames, sharedNodeConfig.IPAddresses...)
return
}

View File

@@ -3,6 +3,7 @@ package nodes
import (
"context"
"crypto/tls"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"io"
@@ -52,6 +53,8 @@ func (this *HTTPListener) Serve() error {
atomic.AddInt64(&this.countActiveConnections, 1)
case http.StateClosed:
atomic.AddInt64(&this.countActiveConnections, -1)
default:
// do nothing
}
},
ConnContext: func(ctx context.Context, conn net.Conn) context.Context {
@@ -74,7 +77,7 @@ func (this *HTTPListener) Serve() error {
// HTTP协议
if this.isHTTP {
err := this.httpServer.Serve(this.Listener)
if err != nil && err != http.ErrServerClosed {
if err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
}
@@ -84,7 +87,7 @@ func (this *HTTPListener) Serve() error {
this.httpServer.TLSConfig = this.buildTLSConfig()
err := this.httpServer.ServeTLS(this.Listener, "", "")
if err != nil && err != http.ErrServerClosed {
if err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
}
@@ -105,17 +108,30 @@ func (this *HTTPListener) Reload(group *serverconfigs.ServerAddressGroup) {
this.Reset()
}
// ServerHTTP 处理HTTP请求
// ServeHTTPWithAddr 处理HTTP请求
func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
this.ServeHTTPWithAddr(rawWriter, rawReq, this.addr)
}
// ServeHTTPWithAddr 处理HTTP请求并指定服务地址
func (this *HTTPListener) ServeHTTPWithAddr(rawWriter http.ResponseWriter, rawReq *http.Request, serverAddr string) {
if len(rawReq.Host) > 253 {
http.Error(rawWriter, "Host too long.", http.StatusBadRequest)
time.Sleep(1 * time.Second) // make connection slow down
return
}
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil && !globalServerConfig.HTTPAll.SupportsLowVersionHTTP && (rawReq.ProtoMajor < 1 /** 0.x **/ || (rawReq.ProtoMajor == 1 && rawReq.ProtoMinor == 0 /** 1.0 **/)) {
http.Error(rawWriter, rawReq.Proto+" request is not supported.", http.StatusBadRequest)
time.Sleep(1 * time.Second) // make connection slow down
return
}
// 不支持Connect
if rawReq.Method == http.MethodConnect {
http.Error(rawWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
time.Sleep(1 * time.Second) // make connection slow down
return
}
@@ -154,7 +170,7 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
domain = reqHost
}
server, serverName := this.findNamedServer(domain)
server, serverName := this.findNamedServer(domain, false)
if server == nil {
if server == nil {
// 增加默认的一个服务
@@ -167,16 +183,24 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
}
// 绑定连接
var clientConn ClientConnInterface
if server != nil && server.Id > 0 {
var requestConn = rawReq.Context().Value(HTTPConnContextKey)
if requestConn != nil {
clientConn, ok := requestConn.(ClientConnInterface)
var ok bool
clientConn, ok = requestConn.(ClientConnInterface)
if ok {
var goNext = clientConn.SetServerId(server.Id)
if !goNext {
return
}
clientConn.SetUserId(server.UserId)
var userPlanId int64
if server.UserPlan != nil && server.UserPlan.Id > 0 {
userPlanId = server.UserPlan.Id
}
clientConn.SetUserPlanId(userPlanId)
}
}
}
@@ -197,7 +221,7 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
ReqServer: server,
ReqHost: reqHost,
ServerName: serverName,
ServerAddr: this.addr,
ServerAddr: serverAddr,
IsHTTP: this.isHTTP,
IsHTTPS: this.isHTTPS,
IsHTTP3: this.isHTTP3,
@@ -205,6 +229,14 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
nodeConfig: sharedNodeConfig,
}
req.Do()
// fix hijacked connection state
if req.isHijacked && clientConn != nil && this.httpServer.ConnState != nil {
netConn, ok := clientConn.(net.Conn)
if ok {
this.httpServer.ConnState(netConn, http.StateClosed)
}
}
}
// 检查host是否为IP

View File

@@ -47,9 +47,13 @@ func (this *TCPListener) Serve() error {
atomic.AddInt64(&this.countActiveConnections, 1)
go func(conn net.Conn) {
err = this.handleConn(conn)
var server = this.Group.FirstServer()
if server == nil {
return
}
err = this.handleConn(server, conn)
if err != nil {
remotelogs.Error("TCP_LISTENER", err.Error())
remotelogs.ServerError(server.Id, "TCP_LISTENER", err.Error(), "", nil)
}
atomic.AddInt64(&this.countActiveConnections, -1)
}(conn)
@@ -63,8 +67,7 @@ func (this *TCPListener) Reload(group *serverconfigs.ServerAddressGroup) {
this.Reset()
}
func (this *TCPListener) handleConn(conn net.Conn) error {
var server = this.Group.FirstServer()
func (this *TCPListener) handleConn(server *serverconfigs.ServerConfig, conn net.Conn) error {
if server == nil {
return errors.New("no server available")
}
@@ -80,6 +83,12 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
return nil
}
clientConn.SetUserId(server.UserId)
var userPlanId int64
if server.UserPlan != nil && server.UserPlan.Id > 0 {
userPlanId = server.UserPlan.Id
}
clientConn.SetUserPlanId(userPlanId)
} else {
tlsConn, ok := conn.(*tls.Conn)
if ok {
@@ -92,6 +101,12 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
return nil
}
clientConn.SetUserId(server.UserId)
var userPlanId int64
if server.UserPlan != nil && server.UserPlan.Id > 0 {
userPlanId = server.UserPlan.Id
}
clientConn.SetUserPlanId(userPlanId)
}
}
}
@@ -120,14 +135,14 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
serverName = tlsConn.ConnectionState().ServerName
if len(serverName) > 0 {
// 统计
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, serverName, 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, serverName, 0, 0, 1, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
recordStat = true
}
}
// 统计
if !recordStat {
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", 0, 0, 1, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
}
originConn, err := this.connectOrigin(server.Id, serverName, server.ReverseProxy, conn.RemoteAddr().String())
@@ -182,7 +197,7 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
// 记录流量
if server != nil {
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
}
}
if err != nil {

View File

@@ -370,7 +370,7 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener
// 统计
if server != nil {
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", 0, 0, 1, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
}
// 处理ControlMessage
@@ -401,10 +401,14 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener
// 记录流量和带宽
if server != nil {
// 流量
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
// 带宽
stats.SharedBandwidthStatManager.AddBandwidth(server.UserId, server.Id, int64(n), int64(n))
var userPlanId int64
if server.UserPlan != nil && server.UserPlan.Id > 0 {
userPlanId = server.UserPlan.Id
}
stats.SharedBandwidthStatManager.AddBandwidth(server.UserId, userPlanId, server.Id, int64(n), int64(n))
}
}
if err != nil {

View File

@@ -139,9 +139,6 @@ func (this *Node) Start() {
remotelogs.Error("NODE", "initialize ip library failed: "+err.Error())
}
// 调整系统参数
this.checkSystem()
// 启动事件
events.Notify(events.EventStart)
@@ -149,12 +146,12 @@ func (this *Node) Start() {
remotelogs.Println("NODE", "init config ...")
err = this.syncConfig(0)
if err != nil {
_, err := nodeconfigs.SharedNodeConfig()
_, err = nodeconfigs.SharedNodeConfig()
if err != nil {
// 无本地数据时,会尝试多次读取
tryTimes := 0
for {
err := this.syncConfig(0)
err = this.syncConfig(0)
if err != nil {
tryTimes++
@@ -208,6 +205,9 @@ func (this *Node) Start() {
sharedNodeConfig = nodeConfig
this.onReload(nodeConfig, true)
// 调整系统参数
go this.tuneSystemParameters()
// 发送事件
events.Notify(events.EventLoaded)
@@ -777,9 +777,23 @@ func (this *Node) listenSock() error {
_ = cmd.ReplyOk()
}
case "gc":
var before = time.Now()
runtime.GC()
debug.FreeOSMemory()
_ = cmd.ReplyOk()
var costSeconds = time.Since(before).Seconds()
var gcStats = &debug.GCStats{}
debug.ReadGCStats(gcStats)
var pauseMS float64
if len(gcStats.Pause) > 0 {
pauseMS = gcStats.Pause[0].Seconds() * 1000
}
_ = cmd.Reply(&gosock.Command{
Params: map[string]any{
"pauseMS": pauseMS,
"costMS": costSeconds * 1000,
},
})
case "reload":
err := this.syncConfig(0)
if err != nil {
@@ -808,6 +822,36 @@ func (this *Node) listenSock() error {
_ = cmd.Reply(&gosock.Command{Params: maps.Map{
"stats": m,
}})
case "cache.garbage":
var shouldDelete = maps.NewMap(cmd.Params).GetBool("delete")
var count = 0
var sampleFiles = []string{}
err := caches.SharedManager.ScanGarbageCaches(func(path string) error {
count++
if len(sampleFiles) < 10 {
sampleFiles = append(sampleFiles, path)
}
if shouldDelete {
_ = os.Remove(path) // .cache
_ = os.Remove(caches.PartialRangesFilePath(path)) // @range.cache
}
return nil
})
if err != nil {
_ = cmd.Reply(&gosock.Command{Params: maps.Map{
"isOk": false,
"error": err.Error(),
}})
} else {
_ = cmd.Reply(&gosock.Command{Params: maps.Map{
"isOk": true,
"count": count,
"sampleFiles": sampleFiles,
}})
}
}
})
@@ -1009,7 +1053,7 @@ func (this *Node) reloadServer() {
for serverId, serverConfig := range updatingServerMap {
if serverConfig != nil {
if countUpdatingServers < maxPrintServers {
remotelogs.Debug("NODE", "load server '"+types.String(serverId)+"'")
remotelogs.Debug("NODE", "reload server '"+types.String(serverId)+"'")
}
newNodeConfig.AddServer(serverConfig)
} else {
@@ -1048,11 +1092,15 @@ func (this *Node) reloadServer() {
}
// 检查系统
func (this *Node) checkSystem() {
func (this *Node) tuneSystemParameters() {
if runtime.GOOS != "linux" || os.Getgid() != 0 {
return
}
if sharedNodeConfig == nil || !sharedNodeConfig.AutoSystemTuning {
return
}
type variable struct {
name string
minValue int
@@ -1061,7 +1109,8 @@ func (this *Node) checkSystem() {
const dir = "/proc/sys"
for _, v := range []variable{
// net
var systemParameters = []variable{
{name: "net.core.somaxconn", minValue: 2048},
{name: "net.ipv4.tcp_max_syn_backlog", minValue: 2048},
{name: "net.core.netdev_max_backlog", minValue: 4096},
@@ -1071,7 +1120,28 @@ func (this *Node) checkSystem() {
{name: "net.core.wmem_default", minValue: 4 << 20},
{name: "net.core.rmem_max", minValue: 32 << 20},
{name: "net.core.wmem_max", minValue: 32 << 20},
} {
}
// vm
var systemMemory = utils.SystemMemoryGB()
if systemMemory >= 128 {
systemParameters = append(systemParameters, []variable{
{name: "vm.dirty_background_ratio", minValue: 40},
{name: "vm.dirty_ratio", minValue: 60},
}...)
} else if systemMemory >= 64 {
systemParameters = append(systemParameters, []variable{
{name: "vm.dirty_background_ratio", minValue: 30},
{name: "vm.dirty_ratio", minValue: 50},
}...)
} else if systemMemory >= 16 {
systemParameters = append(systemParameters, []variable{
{name: "vm.dirty_background_ratio", minValue: 15},
{name: "vm.dirty_ratio", minValue: 30},
}...)
}
for _, v := range systemParameters {
var path = dir + "/" + strings.Replace(v.name, ".", "/", -1)
data, err := os.ReadFile(path)
if err != nil {

View File

@@ -223,6 +223,7 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
// 当前TeaWeb所在的fs
var rootFS = ""
var rootTotal = uint64(0)
var totalUsed = uint64(0)
if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
for _, p := range partitions {
if p.Mountpoint == "/" {
@@ -230,6 +231,7 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
usage, _ := disk.Usage(p.Mountpoint)
if usage != nil {
rootTotal = usage.Total
totalUsed = usage.Used
}
break
}
@@ -237,7 +239,6 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
}
var total = rootTotal
var totalUsage = uint64(0)
var maxUsage = float64(0)
for _, partition := range partitions {
if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") {
@@ -256,15 +257,17 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
if partition.Mountpoint != "/" && (usage.Total != rootTotal || total == 0) {
total += usage.Total
}
totalUsage += usage.Used
if usage.UsedPercent >= maxUsage {
maxUsage = usage.UsedPercent
status.DiskMaxUsagePartition = partition.Mountpoint
totalUsed += usage.Used
if usage.UsedPercent >= maxUsage {
maxUsage = usage.UsedPercent
status.DiskMaxUsagePartition = partition.Mountpoint
}
}
}
status.DiskTotal = total
status.DiskUsage = float64(totalUsage) / float64(total)
if total > 0 {
status.DiskUsage = float64(totalUsed) / float64(total)
}
status.DiskMaxUsage = maxUsage / 100
// 记录监控数据
@@ -280,7 +283,7 @@ func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus)
var result = []maps.Map{}
var cachePaths = caches.SharedManager.FindAllCachePaths()
for _, path := range cachePaths {
stat, err := fsutils.Stat(path)
stat, err := fsutils.StatDevice(path)
if err != nil {
return
}

View File

@@ -4,6 +4,7 @@ package nodes
import (
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -16,9 +17,13 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"os"
"strings"
"time"
)
@@ -93,8 +98,19 @@ func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
err = this.notifyPlusChange()
case "toaChanged":
err = this.execTOAChangedTask()
case "networkSecurityPolicyChanged":
err = this.execNetworkSecurityPolicyChangedTask(rpcClient)
case "webPPolicyChanged":
err = this.execWebPPolicyChangedTask(rpcClient)
case "planChanged":
err = this.execPlanChangedTask(rpcClient)
default:
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
// 特殊任务
if strings.HasPrefix(task.Type, "ipListDeleted") { // 删除IP名单
err = this.execDeleteIPList(task.Type)
} else { // 未处理的任务
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
}
}
return err
@@ -285,6 +301,62 @@ func (this *Node) execUpdatingServersTask(rpcClient *rpc.RPCClient) error {
return nil
}
// 删除IP名单
func (this *Node) execDeleteIPList(taskType string) error {
optionsString, ok := utils.CutPrefix(taskType, "ipListDeleted@")
if !ok {
return errors.New("invalid task type '" + taskType + "'")
}
var optionMap = maps.Map{}
err := json.Unmarshal([]byte(optionsString), &optionMap)
if err != nil {
return fmt.Errorf("decode options failed: %w, options: %s", err, optionsString)
}
var listId = optionMap.GetInt64("listId")
if listId <= 0 {
return nil
}
// 标记已被删除
waf.AddDeletedIPList(listId)
var list = iplibrary.SharedIPListManager.FindList(listId)
if list != nil {
list.SetDeleted()
}
return nil
}
// WebP策略变更
func (this *Node) execWebPPolicyChangedTask(rpcClient *rpc.RPCClient) error {
remotelogs.Println("NODE", "updating webp policies ...")
resp, err := rpcClient.NodeRPC.FindNodeWebPPolicies(rpcClient.Context(), &pb.FindNodeWebPPoliciesRequest{})
if err != nil {
return err
}
var webPPolicyMap = map[int64]*nodeconfigs.WebPImagePolicy{}
for _, policy := range resp.WebPPolicies {
if len(policy.WebPPolicyJSON) > 0 {
var webPPolicy = nodeconfigs.NewWebPImagePolicy()
err = json.Unmarshal(policy.WebPPolicyJSON, webPPolicy)
if err != nil {
remotelogs.Error("NODE", "decode webp policy failed: "+err.Error())
continue
}
err = webPPolicy.Init()
if err != nil {
remotelogs.Error("NODE", "initialize webp policy failed: "+err.Error())
continue
}
webPPolicyMap[policy.NodeClusterId] = webPPolicy
}
}
sharedNodeConfig.UpdateWebPImagePolicies(webPPolicyMap)
return nil
}
// 标记任务完成
func (this *Node) finishTask(taskId int64, taskVersion int64, taskErr error) (success bool) {
if taskId <= 0 {

View File

@@ -29,3 +29,12 @@ func (this *Node) execHTTPPagesPolicyChangedTask(rpcClient *rpc.RPCClient) error
// stub
return nil
}
func (this *Node) execNetworkSecurityPolicyChangedTask(rpcClient *rpc.RPCClient) error {
// stub
return nil
}
func (this *Node) execPlanChangedTask(rpcClient *rpc.RPCClient) error {
return nil
}

Some files were not shown because too many files have changed in this diff Show More