Compare commits

...

141 Commits

Author SHA1 Message Date
刘祥超
51dd778ca7 更改为v0.4.2 2022-02-21 17:52:02 +08:00
刘祥超
b5f706686c 修复热点数据从文件系统转移到内存时可能不完整的Bug/实现部分Partial Content功能 2022-02-21 17:33:58 +08:00
刘祥超
b67c2ec39c 缓存关闭X-Cache显示时从Header中删除X-Cache 2022-02-21 16:55:25 +08:00
刘祥超
d40bc4e72b URL跳转可以设置是否保留参数 2022-02-20 09:17:50 +08:00
刘祥超
94d0fc7e88 当压缩格式不在Accept-Encoding中时自动解压 2022-02-18 11:05:09 +08:00
刘祥超
ceaeba7089 修复文件句柄缓存可能重复加入的Bug 2022-02-17 17:38:56 +08:00
刘祥超
a1e868bf29 读取缓存错误更详细 2022-02-17 17:24:35 +08:00
刘祥超
e60af85819 修复从缓存文件中读取压缩内容时可能失败的Bug 2022-02-17 16:56:13 +08:00
刘祥超
7bd24fcc81 检查是否压缩的时候,如果content-type为空,则默认为text/html 2022-02-15 18:31:37 +08:00
刘祥超
4331223916 优化代码 2022-02-15 16:44:39 +08:00
刘祥超
f50113517a 重构对HTTP请求的处理方法:缓存、压缩、WebP、限速 2022-02-15 14:55:49 +08:00
刘祥超
6d6e25f298 WAF规则提示错误时增加分组ID、规则集ID、规则描述 2022-01-29 21:43:42 +08:00
刘祥超
69c89fda48 支持单个服务更新配置 2022-01-19 22:16:46 +08:00
刘祥超
9a56671457 修改版本为v0.4.1 2022-01-17 10:53:23 +08:00
刘祥超
7dde0deb25 TCP源站也支持证书 2022-01-16 19:58:07 +08:00
刘祥超
b4647b1baa 优化验证码在窄屏上的展示 2022-01-16 16:57:25 +08:00
刘祥超
952e3ca572 CAPTCHA增加多个选项 2022-01-16 16:54:13 +08:00
刘祥超
238973a5e2 删除缓存数据库版本切换时的错误提示 2022-01-14 11:48:39 +08:00
刘祥超
9b6ab2fa8b 优化代码 2022-01-14 11:21:28 +08:00
刘祥超
9591004b70 优化代码 2022-01-13 15:49:42 +08:00
刘祥超
00cd86a8b3 优化open file cache,现在能缓存header 2022-01-13 15:18:49 +08:00
刘祥超
2a1cc63989 优化代码 2022-01-13 11:46:42 +08:00
刘祥超
8177768cf6 优化代码 2022-01-13 11:45:51 +08:00
刘祥超
14d156d42d 改进SYN Flood检测 2022-01-13 11:36:05 +08:00
刘祥超
63992bb2a0 优化代码 2022-01-12 21:41:05 +08:00
刘祥超
d02f9f9a0e 实现open file cache 2022-01-12 21:09:00 +08:00
刘祥超
91fab59a18 优化代码 2022-01-12 20:31:04 +08:00
刘祥超
76c82b431a 优化代码 2022-01-11 17:17:58 +08:00
刘祥超
2f6414fc55 优化代码 2022-01-11 16:02:41 +08:00
刘祥超
e23f4aaee2 优化代码 2022-01-11 09:25:34 +08:00
刘祥超
443660ac38 实现自动SYN Flood防护 2022-01-10 19:54:10 +08:00
刘祥超
488430bbef 优化代码 2022-01-10 15:38:53 +08:00
刘祥超
344de90bff 部分请求增加User-Agent 2022-01-10 10:02:15 +08:00
刘祥超
2f02827cb7 优化编译脚本 2022-01-09 20:12:59 +08:00
刘祥超
03e774cc44 自动使用本地防火墙/增加edge-node [ip.drop|ip.reject|ip.remove]等命令 2022-01-09 17:07:37 +08:00
刘祥超
ff2826ab47 优化一处错误提示 2022-01-09 15:32:02 +08:00
刘祥超
ecaa45db34 优化代码 2022-01-09 10:53:21 +08:00
刘祥超
b6cc826a54 优化正则表达式/修复一些测试用例 2022-01-08 12:20:18 +08:00
刘祥超
b8d7e3f5b4 提升WAF正则表达式性能(提升20%以上) 2022-01-08 11:45:14 +08:00
刘祥超
390be7f6c6 增加${browser.xxx}相关变量 2022-01-06 17:05:04 +08:00
刘祥超
ac4e240912 国家、省份数据不再每个小时更新一次;WAF增加国家/地区、省份、城市、ISP等参数 2022-01-06 16:27:39 +08:00
刘祥超
be7267211b 统计数据上传时如果遇到invalid utf-8,则自动过滤非法字符/统计数据上传失败时,仍然丢弃已有的统计数据,防止数据堆积 2022-01-05 16:05:58 +08:00
刘祥超
88fa75acb5 优化代码 2022-01-03 21:50:51 +08:00
刘祥超
d62fccf0a4 如果源站返回的内容长度为0,则不再尝试读取数据 2022-01-03 18:10:02 +08:00
刘祥超
258ffef0c2 尝试自动在firewalld中开放端口 2022-01-03 16:27:34 +08:00
刘祥超
a41f834192 可以打印服务相关日志信息 2022-01-03 15:53:59 +08:00
刘祥超
00500cb6a3 优化代码 2022-01-02 22:45:37 +08:00
刘祥超
32a3400138 优化代码 2022-01-01 22:02:46 +08:00
刘祥超
5ae4ef665e 优化UserAgent解析 2022-01-01 21:47:59 +08:00
刘祥超
336db828ad 优化代码 2022-01-01 20:15:39 +08:00
刘祥超
a1212804bb 增加edge-node gc命令 2022-01-01 17:18:34 +08:00
刘祥超
763ab4ac98 优化代码 2021-12-31 19:51:56 +08:00
刘祥超
4ec6ae4301 优化文字提示 2021-12-31 19:46:33 +08:00
刘祥超
4f292c5003 如果没有设置节点CPU线程数,则默认为4倍的CPU线程数 2021-12-31 19:45:54 +08:00
刘祥超
a00325f41a 修改版本为0.4.0 2021-12-31 15:19:20 +08:00
刘祥超
fc4490b782 修改版本号0.3.8 2021-12-31 11:36:11 +08:00
刘祥超
7f3f7e21b8 将请求的一些方法改为可exported,方便以后扩展 2021-12-30 11:19:11 +08:00
刘祥超
2525cdc061 根据Accept-Encoding决定是否解压响应内容 2021-12-29 10:57:15 +08:00
刘祥超
4ffc619aad 将获取系统内存函数放入到utils中 2021-12-29 10:53:59 +08:00
刘祥超
f930705fd7 删除不需要的文件 2021-12-24 15:00:49 +08:00
刘祥超
56961c1476 修复测试用例 2021-12-23 14:36:52 +08:00
刘祥超
28b61d493f 优化代码 2021-12-22 16:43:16 +08:00
刘祥超
5cb5ddf2c1 修复并发下,写缓存文件可能冲突的问题 2021-12-21 08:03:09 +08:00
刘祥超
fd0bc37ec7 修复并发下,写缓存文件可能冲突的问题 2021-12-21 00:27:32 +08:00
刘祥超
73666bea7f 修改版本号为0.4.0 2021-12-20 20:01:07 +08:00
刘祥超
462442e21a 缓存数据库升级时从老的数据库转移数据 2021-12-19 18:55:54 +08:00
刘祥超
90fcddfb9f WAF:优化get302/post307代码 2021-12-19 18:54:43 +08:00
刘祥超
8de791079c 优化代码 2021-12-19 16:54:56 +08:00
刘祥超
13b89d5971 当使用quit退出进程时,同时也禁用缓存策略 2021-12-19 14:15:17 +08:00
刘祥超
8b97638624 优化代码 2021-12-19 11:32:26 +08:00
刘祥超
79ea9e795e edge-node conns命令可以打印当前总连接数 2021-12-18 20:13:41 +08:00
刘祥超
38e06e7b03 TLS连接增加握手超时检查 2021-12-18 19:17:40 +08:00
刘祥超
f25de8d5c9 批量清除缓存时延时删除 2021-12-17 11:54:27 +08:00
刘祥超
af74500810 优化代码 2021-12-17 11:54:06 +08:00
刘祥超
189295ffcf X-Cache增加STALE状态 2021-12-17 11:53:59 +08:00
刘祥超
da09889eca 优化代码 2021-12-16 20:36:42 +08:00
刘祥超
9ffa910044 源站没有地址时也尝试Stale Cache/避免write50x()方法进入死循环 2021-12-16 17:38:07 +08:00
刘祥超
a6d711c2a0 实现stale cache读取 2021-12-16 17:27:21 +08:00
刘祥超
6bedc97c95 优化代码 2021-12-15 20:46:10 +08:00
刘祥超
4bdd1eda76 更新依赖 2021-12-15 16:57:06 +08:00
刘祥超
7fd9766565 修复未完成的代码 2021-12-15 15:11:25 +08:00
刘祥超
72983d8d86 优化代码 2021-12-15 15:09:58 +08:00
刘祥超
ec6494fa9c 优化HTTP参数 2021-12-15 13:48:48 +08:00
刘祥超
a4a6e95099 HTTP Header:实现请求方法、域名、状态码等限制,实现内容替换功能 2021-12-14 21:27:24 +08:00
刘祥超
2e11c99b7a 优化代码 2021-12-14 10:49:40 +08:00
刘祥超
014f433191 优化代码 2021-12-14 10:01:21 +08:00
刘祥超
e6ac085025 优化代码 2021-12-13 14:58:24 +08:00
刘祥超
6f60be6a00 服务最大连接数和单IP最大连接数任其一不为0则生效 2021-12-13 11:24:03 +08:00
刘祥超
dceb082a83 修改网络连接错误日志级别 2021-12-13 08:27:39 +08:00
刘祥超
9084794448 路由规则增加专属域名设置 2021-12-12 16:38:38 +08:00
刘祥超
065de8d208 在URL跳转、重写规则跳转、自动跳转到HTTPS等处增加响应Header 2021-12-12 14:10:42 +08:00
刘祥超
e5f9316e33 实现请求连接数等限制 2021-12-12 11:48:01 +08:00
刘祥超
bb5fa38613 实现全局的TCP最大连接数 2021-12-09 17:34:05 +08:00
刘祥超
ccb97b1c79 实现线程数限制 2021-12-09 12:07:59 +08:00
刘祥超
853e4fd0f0 使用空struct{}代替bool节约内存 2021-12-09 12:07:46 +08:00
刘祥超
d3169eaea5 优化代码 2021-12-08 22:19:15 +08:00
刘祥超
68b93bf6b4 可以在缓存条件里设置Expires Header 2021-12-08 17:41:39 +08:00
刘祥超
1279f0d394 优化系统goroutine使用,减少goroutine数量,增加goman查看goroutine数量指令 2021-12-08 15:17:45 +08:00
刘祥超
24fbd740b5 实现记录请求Body 2021-12-07 15:12:15 +08:00
刘祥超
5772fb2309 缓存支持请求方法设置 2021-12-07 10:43:42 +08:00
刘祥超
bf2b889c16 增加${cache.key}变量 2021-12-07 09:28:15 +08:00
刘祥超
9372bc90dd 增加${isArgs}请求变量 2021-12-06 21:47:57 +08:00
刘祥超
5b46c80431 在开发环境下打印Go语言内部HTTP调试信息 2021-12-06 19:28:26 +08:00
刘祥超
1bdb988425 启动时增加sid设置 2021-12-06 19:28:00 +08:00
刘祥超
a544a77669 自动将API节点的IP加入到白名单,防止误封
但要注意:在单个机器上安装API节点和边缘节点,通过局域网IP访问时就无法测试WAF规则,因为会被自动加入到白名单
2021-12-06 10:11:22 +08:00
刘祥超
c61108faa8 优化代码 2021-12-06 08:56:02 +08:00
刘祥超
30ac3118e2 国家/地区统计时上传流量、攻击量等信息 2021-12-05 18:57:30 +08:00
刘祥超
f0e8dd1baa 缓存增加UPDATING状态 2021-12-05 17:10:06 +08:00
刘祥超
04a327ce9a 修复源站主动关闭连接时无法缓存内容的Bug 2021-12-05 16:55:33 +08:00
刘祥超
2ac26f6aa4 回源主机名为“跟随源站”时,获得的源站主机名去除常规端口80和443 2021-12-05 09:30:45 +08:00
刘祥超
d9aac44ea3 WAF忽略客户端断开连接错误 2021-12-04 19:28:02 +08:00
刘祥超
160a1f1466 优化通过IP查询区域性能 2021-12-03 15:51:28 +08:00
刘祥超
38e2c151ec 降低ttlcache最大内存增量 2021-12-03 10:22:03 +08:00
刘祥超
9d54c17695 支持规则集忽略局域网IP 2021-12-02 16:08:25 +08:00
刘祥超
e31d68c1e1 将RPC连接错误级别从error改为warning 2021-12-02 15:14:47 +08:00
刘祥超
7ae9180bf9 多个提示页面增加请求ID、增加变量支持 2021-12-02 14:46:40 +08:00
刘祥超
424f3ae29d 优化验证码页面 2021-12-02 12:08:59 +08:00
刘祥超
ca0571a21b 增加${requestId}变量 2021-12-02 11:30:47 +08:00
刘祥超
c9bd9fd460 URL跳转时检查前后跳转的URL是否一致,防止无限跳转 2021-12-02 10:35:51 +08:00
刘祥超
8d4ec6822c 缓存支持源站设置的max-age 2021-12-02 10:19:02 +08:00
刘祥超
061253b4c3 缓存在响应中可以添加Age Header 2021-12-02 09:54:48 +08:00
刘祥超
f6dfd6acec 增加${cache.age}变量 2021-12-02 09:34:38 +08:00
刘祥超
a35aa2f520 增加是否记录499选项 2021-12-01 21:13:10 +08:00
刘祥超
ea84c41be3 因WAF规则拦截而关闭连接时,不记录499 2021-12-01 20:55:19 +08:00
刘祥超
0f0776fc1a 修复WAF OnAction在并发时无法准确调用请求动作的Bug 2021-12-01 17:43:08 +08:00
刘祥超
6aacf49764 可以上报服务相关配置错误 2021-12-01 15:52:38 +08:00
刘祥超
18a01b9b43 上传访问日志时如果出现string field contains invalid UTF-8,则重新处理后提交 2021-12-01 14:25:55 +08:00
刘祥超
32a3e08332 优化编译脚本 2021-12-01 14:24:56 +08:00
刘祥超
2397695a2d 优化服务日志 2021-11-30 16:43:58 +08:00
刘祥超
0ceebd9902 端口提示被占用时提示语中加入当前占用端口的进程名 2021-11-30 15:03:46 +08:00
刘祥超
7e62c72b79 修复TOA管理中可能出现的panic错误 2021-11-29 11:15:14 +08:00
刘祥超
b3dedbdc31 健康检查不使用密钥加密 2021-11-29 09:52:31 +08:00
刘祥超
fa967b5450 修改版本号为0.3.7 2021-11-28 14:28:24 +08:00
刘祥超
b619eb4efe 修复WAF中scheme checkpoint值为空的问题 2021-11-26 13:42:04 +08:00
刘祥超
7763f26249 修复WAF的临时白名单被当做黑名单使用的Bug 2021-11-26 10:39:04 +08:00
刘祥超
b7ae10e2d0 修复合并URL中多余分隔符时导致参数发生变化的Bug 2021-11-24 15:01:06 +08:00
刘祥超
e54eddc961 服务增加是否合并URL中的多余分隔符选项 2021-11-24 14:50:07 +08:00
刘祥超
93db9d4926 版本号改为0.3.6 2021-11-24 14:04:01 +08:00
刘祥超
8c1af3e699 修复ipset无法提前删除IP的Bug 2021-11-24 10:21:02 +08:00
刘祥超
53c74553bc 修复ipset无法提前删除IP的Bug 2021-11-24 10:20:06 +08:00
刘祥超
3eb9cade0e 修改版本为0.3.5.2 2021-11-24 10:19:36 +08:00
219 changed files with 8486 additions and 2902 deletions

View File

@@ -5,6 +5,10 @@ function build() {
NAME="edge-node" NAME="edge-node"
VERSION=$(lookup-version $ROOT/../internal/const/const.go) VERSION=$(lookup-version $ROOT/../internal/const/const.go)
DIST=$ROOT/"../dist/${NAME}" 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"
OS=${1} OS=${1}
ARCH=${2} ARCH=${2}
TAG=${3} TAG=${3}
@@ -53,22 +57,41 @@ function build() {
echo "building ..." echo "building ..."
MUSL_DIR="/usr/local/opt/musl-cross/bin"
CC_PATH="" CC_PATH=""
CXX_PATH="" CXX_PATH=""
BUILD_TAG=$TAG
if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then
# /usr/local/opt/musl-cross/bin/
if [ "${ARCH}" == "amd64" ]; then if [ "${ARCH}" == "amd64" ]; then
CC_PATH="x86_64-linux-musl-gcc" # build with script support
CXX_PATH="x86_64-linux-musl-g++" if [ -d $GCC_X86_64_DIR ]; then
MUSL_DIR=$GCC_X86_64_DIR
CC_PATH="x86_64-unknown-linux-gnu-gcc"
CXX_PATH="x86_64-unknown-linux-gnu-g++"
if [ "$TAG" = "plus" ]; then
BUILD_TAG="plus,script"
fi
else
CC_PATH="x86_64-linux-musl-gcc"
CXX_PATH="x86_64-linux-musl-g++"
fi
fi fi
if [ "${ARCH}" == "386" ]; then if [ "${ARCH}" == "386" ]; then
CC_PATH="i486-linux-musl-gcc" CC_PATH="i486-linux-musl-gcc"
CXX_PATH="i486-linux-musl-g++" CXX_PATH="i486-linux-musl-g++"
fi fi
if [ "${ARCH}" == "arm64" ]; then if [ "${ARCH}" == "arm64" ]; then
CC_PATH="aarch64-linux-musl-gcc" # build with script support
CXX_PATH="aarch64-linux-musl-g++" if [ -d $GCC_ARM64_DIR ]; then
MUSL_DIR=$GCC_ARM64_DIR
CC_PATH="aarch64-unknown-linux-gnu-gcc"
CXX_PATH="aarch64-unknown-linux-gnu-g++"
if [ "$TAG" = "plus" ]; then
BUILD_TAG="plus,script"
fi
else
CC_PATH="aarch64-linux-musl-gcc"
CXX_PATH="aarch64-linux-musl-g++"
fi
fi fi
if [ "${ARCH}" == "arm" ]; then if [ "${ARCH}" == "arm" ]; then
CC_PATH="arm-linux-musleabi-gcc" CC_PATH="arm-linux-musleabi-gcc"
@@ -84,7 +107,7 @@ function build() {
fi fi
fi fi
if [ ! -z $CC_PATH ]; then 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 -tags $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 go build -tags $BUILD_TAG -o $DIST/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" $ROOT/../cmd/edge-node/main.go
else else
env GOOS=${OS} GOARCH=${ARCH} CGO_ENABLED=1 go build -tags $TAG -o $DIST/bin/${NAME} -ldflags="-s -w" $ROOT/../cmd/edge-node/main.go env GOOS=${OS} GOARCH=${ARCH} CGO_ENABLED=1 go build -tags $TAG -o $DIST/bin/${NAME} -ldflags="-s -w" $ROOT/../cmd/edge-node/main.go
fi fi

View File

@@ -1,13 +1,17 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeNode/internal/apps" "github.com/TeaOSLab/EdgeNode/internal/apps"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/nodes" "github.com/TeaOSLab/EdgeNode/internal/nodes"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"net"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
@@ -88,6 +92,150 @@ func main() {
} }
} }
}) })
app.On("goman", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "goman"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
instancesJSON, err := json.MarshalIndent(reply.Params, "", " ")
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
fmt.Println(string(instancesJSON))
}
}
})
app.On("conns", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "conns"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
resultJSON, err := json.MarshalIndent(reply.Params, "", " ")
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
fmt.Println(string(resultJSON))
}
}
})
app.On("gc", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
_, err := sock.Send(&gosock.Command{Code: "gc"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
fmt.Println("ok")
}
})
app.On("ip.drop", func() {
var args = os.Args[2:]
if len(args) == 0 {
fmt.Println("Usage: edge-node ip.drop IP [--timeout=SECONDS]")
return
}
var ip = args[0]
if len(net.ParseIP(ip)) == 0 {
fmt.Println("IP '" + ip + "' is invalid")
return
}
var timeoutSeconds = 0
var options = app.ParseOptions(args[1:])
timeout, ok := options["timeout"]
if ok {
timeoutSeconds = types.Int(timeout[0])
}
fmt.Println("drop ip '" + ip + "' for '" + types.String(timeoutSeconds) + "' seconds")
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{
Code: "dropIP",
Params: map[string]interface{}{
"ip": ip,
"timeoutSeconds": timeoutSeconds,
},
})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var errString = maps.NewMap(reply.Params).GetString("error")
if len(errString) > 0 {
fmt.Println("[ERROR]" + errString)
} else {
fmt.Println("ok")
}
}
})
app.On("ip.reject", func() {
var args = os.Args[2:]
if len(args) == 0 {
fmt.Println("Usage: edge-node ip.reject IP [--timeout=SECONDS]")
return
}
var ip = args[0]
if len(net.ParseIP(ip)) == 0 {
fmt.Println("IP '" + ip + "' is invalid")
return
}
var timeoutSeconds = 0
var options = app.ParseOptions(args[1:])
timeout, ok := options["timeout"]
if ok {
timeoutSeconds = types.Int(timeout[0])
}
fmt.Println("reject ip '" + ip + "' for '" + types.String(timeoutSeconds) + "' seconds")
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{
Code: "rejectIP",
Params: map[string]interface{}{
"ip": ip,
"timeoutSeconds": timeoutSeconds,
},
})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var errString = maps.NewMap(reply.Params).GetString("error")
if len(errString) > 0 {
fmt.Println("[ERROR]" + errString)
} else {
fmt.Println("ok")
}
}
})
app.On("ip.remove", func() {
var args = os.Args[2:]
if len(args) == 0 {
fmt.Println("Usage: edge-node ip.remove IP")
return
}
var ip = args[0]
if len(net.ParseIP(ip)) == 0 {
fmt.Println("IP '" + ip + "' is invalid")
return
}
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{
Code: "removeIP",
Params: map[string]interface{}{
"ip": ip,
},
})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var errString = maps.NewMap(reply.Params).GetString("error")
if len(errString) > 0 {
fmt.Println("[ERROR]" + errString)
} else {
fmt.Println("ok")
}
}
})
app.Run(func() { app.Run(func() {
node := nodes.NewNode() node := nodes.NewNode()
node.Start() node.Start()

30
go.mod
View File

@@ -2,39 +2,39 @@ module github.com/TeaOSLab/EdgeNode
go 1.15 go 1.15
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon replace (
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
)
require ( require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/andybalholm/brotli v1.0.3 github.com/andybalholm/brotli v1.0.3
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash v1.1.0
github.com/chai2010/webp v1.1.0 // indirect github.com/chai2010/webp v1.1.0 // indirect
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-yaml/yaml v2.1.0+incompatible github.com/go-yaml/yaml v2.1.0+incompatible
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.2
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24 github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8 github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8
github.com/json-iterator/go v1.1.12 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-sqlite3 v1.14.9 github.com/mattn/go-sqlite3 v1.14.9
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/mssola/user_agent v0.5.3
github.com/mssola/user_agent v0.5.2
github.com/pires/go-proxyproto v0.6.1 github.com/pires/go-proxyproto v0.6.1
github.com/shirou/gopsutil v3.21.5+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/tklauser/go-sysconf v0.3.6 // indirect github.com/tklauser/go-sysconf v0.3.6 // indirect
golang.org/x/image v0.0.0-20190802002840-cff245a6509b github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9
golang.org/x/text v0.3.6 golang.org/x/sys v0.0.0-20220111092808-5a964db01320
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect golang.org/x/text v0.3.7
google.golang.org/grpc v1.38.0 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.43.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
) )

101
go.sum
View File

@@ -1,14 +1,14 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM= github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM= 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/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
@@ -22,6 +22,11 @@ github.com/chai2010/webp v1.1.0/go.mod h1:LP12PG5IFmLGHUU26tBiCBKnghxx3toZFwDjOY
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -29,24 +34,22 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 h1:CtSi0QlA2Hy+nOh8JAZoiEBLW5pliAiKJ3l1Iq1472I=
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g= github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
@@ -63,6 +66,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -75,8 +79,8 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060 h1:qdLtK4PDXxk2vMKkTWl5Fl9xqYuRCukzWAgJbLHdfOo=
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24 h1:1cGulkD2SNJJRok5OKwyhP/Ddm+PgSWKOupn0cR36/A= github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24 h1:1cGulkD2SNJJRok5OKwyhP/Ddm+PgSWKOupn0cR36/A=
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
@@ -87,8 +91,6 @@ github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8 h1:AojsHz9Es9B3He2MQQxeRq3TyD//o9huxUo7r1wh44g= github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8 h1:AojsHz9Es9B3He2MQQxeRq3TyD//o9huxUo7r1wh44g=
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8/go.mod h1:QJBY2txYhLMzwLO29iB5ujDJ3s3V7DsZ582nw4Ss+tM= github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8/go.mod h1:QJBY2txYhLMzwLO29iB5ujDJ3s3V7DsZ582nw4Ss+tM=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk= 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/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -98,22 +100,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible h1:1qp9iks+69h7IGLazAplzS9Ca14HAxuD5c0rbFdPGy4=
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= 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.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mssola/user_agent v0.5.3/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo=
github.com/mssola/user_agent v0.5.2/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -129,53 +123,61 @@ github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9G
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4= github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo= go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -185,6 +187,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -197,25 +200,25 @@ golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d h1:1oIt9o40TWWI9FUaveVpUvBe13FNqBNVXy3ue2fcfkw=
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@@ -225,17 +228,21 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -246,8 +253,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -257,6 +265,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -11,6 +11,8 @@ import (
"os/exec" "os/exec"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"syscall"
"time" "time"
) )
@@ -187,6 +189,11 @@ func (this *AppCmd) runStart() {
_ = os.Setenv("EdgeBackground", "on") _ = os.Setenv("EdgeBackground", "on")
cmd := exec.Command(os.Args[0]) cmd := exec.Command(os.Args[0])
cmd.SysProcAttr = &syscall.SysProcAttr{
Foreground: false,
Setsid: true,
}
err := cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
fmt.Println(this.product+" start failed:", err.Error()) fmt.Println(this.product+" start failed:", err.Error())
@@ -239,3 +246,19 @@ func (this *AppCmd) getPID() int {
} }
return maps.NewMap(reply.Params).GetInt("pid") return maps.NewMap(reply.Params).GetInt("pid")
} }
// ParseOptions 分析参数中的选项
func (this *AppCmd) ParseOptions(args []string) map[string][]string {
var result = map[string][]string{}
for _, arg := range args {
var pieces = strings.SplitN(arg, "=", 2)
var key = strings.TrimLeft(pieces[0], "- ")
key = strings.TrimSpace(key)
var value = ""
if len(pieces) == 2 {
value = strings.TrimSpace(pieces[1])
}
result[key] = append(result[key], value)
}
return result
}

View File

@@ -22,6 +22,7 @@ type Item struct {
Type ItemType `json:"type"` Type ItemType `json:"type"`
Key string `json:"key"` Key string `json:"key"`
ExpiredAt int64 `json:"expiredAt"` ExpiredAt int64 `json:"expiredAt"`
StaleAt int64 `json:"staleAt"`
HeaderSize int64 `json:"headerSize"` HeaderSize int64 `json:"headerSize"`
BodySize int64 `json:"bodySize"` BodySize int64 `json:"bodySize"`
MetaSize int64 `json:"metaSize"` MetaSize int64 `json:"metaSize"`

View File

@@ -3,6 +3,7 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"runtime" "runtime"
@@ -59,15 +60,15 @@ func TestItems_Memory2(t *testing.T) {
runtime.ReadMemStats(stat) runtime.ReadMemStats(stat)
var memory1 = stat.HeapInuse var memory1 = stat.HeapInuse
var items = map[int32]map[string]bool{} var items = map[int32]map[string]zero.Zero{}
for i := 0; i < 10_000_000; i++ { for i := 0; i < 10_000_000; i++ {
var week = int32((time.Now().Unix() - int64(86400*rands.Int(0, 300))) / (86400 * 7)) var week = int32((time.Now().Unix() - int64(86400*rands.Int(0, 300))) / (86400 * 7))
m, ok := items[week] m, ok := items[week]
if !ok { if !ok {
m = map[string]bool{} m = map[string]zero.Zero{}
items[week] = m items[week] = m
} }
m[types.String(int64(i)*1_000_000)] = true m[types.String(int64(i)*1_000_000)] = zero.New()
} }
runtime.ReadMemStats(stat) runtime.ReadMemStats(stat)

View File

@@ -4,14 +4,16 @@ package caches
import ( import (
"database/sql" "database/sql"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache" "github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"os" "os"
"strconv"
"sync/atomic" "sync/atomic"
"time" "time"
) )
@@ -45,6 +47,7 @@ type FileList struct {
hitsTableName string hitsTableName string
isClosed bool isClosed bool
isReady bool
memoryCache *ttlcache.Cache memoryCache *ttlcache.Cache
} }
@@ -67,7 +70,7 @@ func (this *FileList) Init() error {
remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'") remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'")
} }
this.itemsTableName = "cacheItems_v2" this.itemsTableName = "cacheItems_v3"
this.hitsTableName = "hits" this.hitsTableName = "hits"
var dir = this.dir var dir = this.dir
@@ -75,21 +78,16 @@ func (this *FileList) Init() error {
// 防止sqlite提示authority错误 // 防止sqlite提示authority错误
dir = "" dir = ""
} }
db, err := sql.Open("sqlite3", "file:"+dir+"/index.db?cache=shared&mode=rwc&_journal_mode=WAL") var dbPath = dir + "/index.db"
remotelogs.Println("CACHE", "loading database '"+dbPath+"'")
db, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=shared&mode=rwc&_journal_mode=WAL")
if err != nil { if err != nil {
return err return errors.New("open database failed: " + err.Error())
} }
db.SetMaxOpenConns(1)
this.db = db
// 清除旧表 db.SetMaxOpenConns(1)
this.oldTables = []string{
"cacheItems", this.db = db
}
err = this.removeOldTables()
if err != nil {
remotelogs.Warn("CACHE", "clean old tables failed: "+err.Error())
}
// TODO 耗时过长,暂时不整理数据库 // TODO 耗时过长,暂时不整理数据库
/**_, err = db.Exec("VACUUM") /**_, err = db.Exec("VACUUM")
@@ -100,7 +98,18 @@ func (this *FileList) Init() error {
// 创建 // 创建
err = this.initTables(db, 1) err = this.initTables(db, 1)
if err != nil { if err != nil {
return err return errors.New("init tables failed: " + err.Error())
}
// 清除旧表
// 这个一定要在initTables()之后,因为老的数据需要转移
this.oldTables = []string{
"cacheItems",
"cacheItems_v2",
}
err = this.removeOldTables()
if err != nil {
remotelogs.Warn("CACHE", "clean old tables failed: "+err.Error())
} }
// 读取总数量 // 读取总数量
@@ -121,7 +130,7 @@ func (this *FileList) Init() error {
return err return err
} }
this.insertStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`) this.insertStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil { if err != nil {
return err return err
} }
@@ -141,7 +150,7 @@ func (this *FileList) Init() error {
return err return err
} }
this.purgeStmt, err = this.db.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" WHERE expiredAt<=? LIMIT ?`) this.purgeStmt, err = this.db.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" WHERE staleAt<=? LIMIT ?`)
if err != nil { if err != nil {
return err return err
} }
@@ -168,6 +177,8 @@ func (this *FileList) Init() error {
return err return err
} }
this.isReady = true
return nil return nil
} }
@@ -177,11 +188,15 @@ func (this *FileList) Reset() error {
} }
func (this *FileList) Add(hash string, item *Item) error { func (this *FileList) Add(hash string, item *Item) error {
if this.isClosed { if !this.isReady {
return nil return nil
} }
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.Host, item.ServerId, utils.UnixTime()) if item.StaleAt == 0 {
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, utils.UnixTime())
if err != nil { if err != nil {
return err return err
} }
@@ -191,6 +206,7 @@ func (this *FileList) Add(hash string, item *Item) error {
return err return err
} }
this.memoryCache.Write(hash, 1, item.ExpiredAt)
atomic.AddInt64(&this.total, 1) atomic.AddInt64(&this.total, 1)
if this.onAdd != nil { if this.onAdd != nil {
@@ -200,7 +216,7 @@ func (this *FileList) Add(hash string, item *Item) error {
} }
func (this *FileList) Exist(hash string) (bool, error) { func (this *FileList) Exist(hash string) (bool, error) {
if this.isClosed { if !this.isReady {
return false, nil return false, nil
} }
@@ -230,7 +246,7 @@ func (this *FileList) Exist(hash string) (bool, error) {
// CleanPrefix 清理某个前缀的缓存数据 // CleanPrefix 清理某个前缀的缓存数据
func (this *FileList) CleanPrefix(prefix string) error { func (this *FileList) CleanPrefix(prefix string) error {
if this.isClosed { if !this.isReady {
return nil return nil
} }
@@ -243,8 +259,10 @@ func (this *FileList) CleanPrefix(prefix string) error {
}() }()
var count = int64(10000) var count = int64(10000)
var staleLife = 600 // TODO 需要可以设置
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
for { for {
result, err := this.db.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0 WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+strconv.FormatInt(count, 10)+`)`, utils.UnixTime(), prefix) result, err := this.db.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)
if err != nil { if err != nil {
return err return err
} }
@@ -259,7 +277,7 @@ func (this *FileList) CleanPrefix(prefix string) error {
} }
func (this *FileList) Remove(hash string) error { func (this *FileList) Remove(hash string) error {
if this.isClosed { if !this.isReady {
return nil return nil
} }
@@ -303,7 +321,7 @@ func (this *FileList) Remove(hash string) error {
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间 // count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
// callback 每次发现过期key的调用 // callback 每次发现过期key的调用
func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) { func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) {
if this.isClosed { if !this.isReady {
return 0, nil return 0, nil
} }
@@ -347,7 +365,7 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
} }
func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error { func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error {
if this.isClosed { if !this.isReady {
return nil return nil
} }
@@ -390,7 +408,7 @@ func (this *FileList) PurgeLFU(count int, callback func(hash string) error) erro
} }
func (this *FileList) CleanAll() error { func (this *FileList) CleanAll() error {
if this.isClosed { if !this.isReady {
return nil return nil
} }
@@ -405,7 +423,7 @@ func (this *FileList) CleanAll() error {
} }
func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) { func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
if this.isClosed { if !this.isReady {
return &Stat{}, nil return &Stat{}, nil
} }
@@ -448,6 +466,7 @@ func (this *FileList) OnRemove(f func(item *Item)) {
func (this *FileList) Close() error { func (this *FileList) Close() error {
this.isClosed = true this.isClosed = true
this.isReady = false
this.memoryCache.Destroy() this.memoryCache.Destroy()
@@ -472,7 +491,16 @@ func (this *FileList) Close() error {
// 初始化 // 初始化
func (this *FileList) initTables(db *sql.DB, times int) error { func (this *FileList) initTables(db *sql.DB, times int) error {
// 检查是否存在
_, err := db.Exec(`SELECT id FROM "` + this.itemsTableName + `" LIMIT 1`)
var notFound = false
if err != nil {
notFound = true
}
{ {
// expiredAt - 过期时间,用来判断有无过期
// staleAt - 陈旧最大时间,用来清理缓存
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" ( _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32), "hash" varchar(32),
@@ -481,6 +509,7 @@ func (this *FileList) initTables(db *sql.DB, times int) error {
"bodySize" integer DEFAULT 0, "bodySize" integer DEFAULT 0,
"metaSize" integer DEFAULT 0, "metaSize" integer DEFAULT 0,
"expiredAt" integer DEFAULT 0, "expiredAt" integer DEFAULT 0,
"staleAt" integer DEFAULT 0,
"createdAt" integer DEFAULT 0, "createdAt" integer DEFAULT 0,
"host" varchar(128), "host" varchar(128),
"serverId" integer "serverId" integer
@@ -496,6 +525,11 @@ ON "` + this.itemsTableName + `" (
"expiredAt" ASC "expiredAt" ASC
); );
CREATE INDEX IF NOT EXISTS "staleAt"
ON "` + this.itemsTableName + `" (
"staleAt" ASC
);
CREATE UNIQUE INDEX IF NOT EXISTS "hash" CREATE UNIQUE INDEX IF NOT EXISTS "hash"
ON "` + this.itemsTableName + `" ( ON "` + this.itemsTableName + `" (
"hash" ASC "hash" ASC
@@ -506,6 +540,7 @@ ON "` + this.itemsTableName + `" (
"serverId" ASC "serverId" ASC
); );
`) `)
if err != nil { if err != nil {
// 尝试删除重建 // 尝试删除重建
if times < 3 { if times < 3 {
@@ -520,6 +555,17 @@ ON "` + this.itemsTableName + `" (
} }
} }
// 如果数据为空,从老数据中加载数据
if notFound {
// v2 => v3
remotelogs.Println("CACHE", "transferring old data from v2 to v3 ...")
result, err := db.Exec(`INSERT INTO "` + this.itemsTableName + `" ("id", "hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "createdAt", "host", "serverId", "staleAt") SELECT "id", "hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "createdAt", "host", "serverId", "expiredAt"+600 FROM cacheItems_v2`)
if err == nil {
count, _ := result.RowsAffected()
remotelogs.Println("CACHE", "transfer old data from v2 to v3 finished, "+types.String(count)+" rows transferred")
}
}
{ {
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" ( _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
@@ -568,11 +614,11 @@ func (this *FileList) removeOldTables() error {
} }
if lists.ContainsString(this.oldTables, name) { if lists.ContainsString(this.oldTables, name) {
// 异步执行 // 异步执行
go func() { goman.New(func() {
remotelogs.Println("CACHE", "remove old table '"+name+"' ...") remotelogs.Println("CACHE", "remove old table '"+name+"' ...")
_, _ = this.db.Exec(`DROP TABLE "` + name + `"`) _, _ = this.db.Exec(`DROP TABLE "` + name + `"`)
remotelogs.Println("CACHE", "remove old table '"+name+"' done") remotelogs.Println("CACHE", "remove old table '"+name+"' done")
}() })
} }
} }

View File

@@ -3,6 +3,7 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -127,7 +128,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
}() }()
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
go func() { goman.New(func() {
defer wg.Done() defer wg.Done()
for { for {
@@ -143,7 +144,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
return return
} }
} }
}() })
} }
wg.Wait() wg.Wait()
t.Log("left:", count) t.Log("left:", count)

View File

@@ -1,6 +1,7 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"strconv" "strconv"
"strings" "strings"
@@ -15,7 +16,7 @@ type MemoryList struct {
itemMaps map[string]map[string]*Item // prefix => { hash => item } itemMaps map[string]map[string]*Item // prefix => { hash => item }
weekItemMaps map[int32]map[string]bool // week => { hash => true } weekItemMaps map[int32]map[string]zero.Zero // week => { hash => Zero }
minWeek int32 minWeek int32
prefixes []string prefixes []string
@@ -29,7 +30,7 @@ type MemoryList struct {
func NewMemoryList() ListInterface { func NewMemoryList() ListInterface {
return &MemoryList{ return &MemoryList{
itemMaps: map[string]map[string]*Item{}, itemMaps: map[string]map[string]*Item{},
weekItemMaps: map[int32]map[string]bool{}, weekItemMaps: map[int32]map[string]zero.Zero{},
minWeek: currentWeek(), minWeek: currentWeek(),
} }
} }
@@ -52,7 +53,7 @@ func (this *MemoryList) Reset() error {
for key := range this.itemMaps { for key := range this.itemMaps {
this.itemMaps[key] = map[string]*Item{} this.itemMaps[key] = map[string]*Item{}
} }
this.weekItemMaps = map[int32]map[string]bool{} this.weekItemMaps = map[int32]map[string]zero.Zero{}
this.locker.Unlock() this.locker.Unlock()
atomic.StoreInt64(&this.count, 0) atomic.StoreInt64(&this.count, 0)
@@ -103,9 +104,9 @@ func (this *MemoryList) Add(hash string, item *Item) error {
// week map // week map
wm, ok := this.weekItemMaps[item.Week] wm, ok := this.weekItemMaps[item.Week]
if ok { if ok {
wm[hash] = true wm[hash] = zero.New()
} else { } else {
this.weekItemMaps[item.Week] = map[string]bool{hash: true} this.weekItemMaps[item.Week] = map[string]zero.Zero{hash: zero.New()}
} }
this.locker.Unlock() this.locker.Unlock()
@@ -381,9 +382,9 @@ func (this *MemoryList) IncreaseHit(hash string) error {
} }
wm, ok = this.weekItemMaps[week] wm, ok = this.weekItemMaps[week]
if ok { if ok {
wm[hash] = true wm[hash] = zero.New()
} else { } else {
this.weekItemMaps[week] = map[string]bool{hash: true} this.weekItemMaps[week] = map[string]zero.Zero{hash: zero.New()}
} }
} }

View File

@@ -3,8 +3,10 @@ package caches
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"strconv" "strconv"
"sync" "sync"
@@ -12,6 +14,13 @@ import (
var SharedManager = NewManager() var SharedManager = NewManager()
func init() {
events.On(events.EventQuit, func() {
logs.Println("CACHE", "quiting cache manager")
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
})
}
// Manager 缓存策略管理器 // Manager 缓存策略管理器
type Manager struct { type Manager struct {
// 全局配置 // 全局配置
@@ -25,10 +34,12 @@ type Manager struct {
// NewManager 获取管理器对象 // NewManager 获取管理器对象
func NewManager() *Manager { func NewManager() *Manager {
return &Manager{ var m = &Manager{
policyMap: map[int64]*serverconfigs.HTTPCachePolicy{}, policyMap: map[int64]*serverconfigs.HTTPCachePolicy{},
storageMap: map[int64]StorageInterface{}, storageMap: map[int64]StorageInterface{},
} }
return m
} }
// UpdatePolicies 重新设置策略 // UpdatePolicies 重新设置策略
@@ -74,7 +85,7 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
for _, policy := range this.policyMap { for _, policy := range this.policyMap {
storage, ok := this.storageMap[policy.Id] storage, ok := this.storageMap[policy.Id]
if !ok { if !ok {
storage := this.NewStorageWithPolicy(policy) storage = this.NewStorageWithPolicy(policy)
if storage == nil { if storage == nil {
remotelogs.Error("CACHE", "can not find storage type '"+policy.Type+"'") remotelogs.Error("CACHE", "can not find storage type '"+policy.Type+"'")
continue continue
@@ -95,7 +106,7 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
delete(this.storageMap, policy.Id) delete(this.storageMap, policy.Id)
// 启动新的 // 启动新的
storage := this.NewStorageWithPolicy(policy) storage = this.NewStorageWithPolicy(policy)
if storage == nil { if storage == nil {
remotelogs.Error("CACHE", "can not find storage type '"+policy.Type+"'") remotelogs.Error("CACHE", "can not find storage type '"+policy.Type+"'")
continue continue

View File

@@ -0,0 +1,33 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
import (
"io"
"os"
)
type OpenFile struct {
fp *os.File
meta []byte
header []byte
version int64
}
func NewOpenFile(fp *os.File, meta []byte, header []byte) *OpenFile {
return &OpenFile{
fp: fp,
meta: meta,
header: header,
}
}
func (this *OpenFile) SeekStart() error {
_, err := this.fp.Seek(0, io.SeekStart)
return err
}
func (this *OpenFile) Close() error {
this.meta = nil
return this.fp.Close()
}

View File

@@ -0,0 +1,161 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
"github.com/fsnotify/fsnotify"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types"
"path/filepath"
"sync"
"time"
)
type OpenFileCache struct {
poolMap map[string]*OpenFilePool // file path => Pool
poolList *linkedlist.List
watcher *fsnotify.Watcher
locker sync.Mutex
maxSize int
count int
}
func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
if maxSize <= 0 {
maxSize = 16384
}
var cache = &OpenFileCache{
maxSize: maxSize,
poolMap: map[string]*OpenFilePool{},
poolList: linkedlist.NewList(),
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
cache.watcher = watcher
goman.New(func() {
for event := range watcher.Events {
if event.Op&fsnotify.Chmod != fsnotify.Chmod {
cache.Close(event.Name)
}
}
})
return cache, nil
}
func (this *OpenFileCache) Get(filename string) *OpenFile {
this.locker.Lock()
defer this.locker.Unlock()
pool, ok := this.poolMap[filename]
if ok {
file, consumed := pool.Get()
if consumed {
this.count--
}
return file
}
return nil
}
func (this *OpenFileCache) Put(filename string, file *OpenFile) {
this.locker.Lock()
defer this.locker.Unlock()
pool, ok := this.poolMap[filename]
var success bool
if ok {
success = pool.Put(file)
} else {
_ = this.watcher.Add(filename)
pool = NewOpenFilePool(filename)
this.poolMap[filename] = pool
success = pool.Put(file)
}
this.poolList.Push(pool.linkItem)
// 检查长度
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)
}
}
}
}
}
func (this *OpenFileCache) Close(filename string) {
this.locker.Lock()
pool, ok := this.poolMap[filename]
if ok {
delete(this.poolMap, filename)
this.poolList.Remove(pool.linkItem)
_ = this.watcher.Remove(filename)
this.count -= pool.Len()
}
this.locker.Unlock()
// 在locker之外提升性能
if ok {
pool.Close()
}
}
func (this *OpenFileCache) CloseAll() {
this.locker.Lock()
for _, pool := range this.poolMap {
pool.Close()
}
this.poolMap = map[string]*OpenFilePool{}
this.poolList.Reset()
_ = this.watcher.Close()
this.locker.Unlock()
}
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())
return true
})
}
})
}

View File

@@ -0,0 +1,76 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
)
type OpenFilePool struct {
c chan *OpenFile
linkItem *linkedlist.Item
filename string
version int64
}
func NewOpenFilePool(filename string) *OpenFilePool {
var pool = &OpenFilePool{
filename: filename,
c: make(chan *OpenFile, 1024),
version: utils.UnixTimeMilli(),
}
pool.linkItem = linkedlist.NewItem(pool)
return pool
}
func (this *OpenFilePool) Filename() string {
return this.filename
}
func (this *OpenFilePool) Get() (*OpenFile, bool) {
select {
case file := <-this.c:
err := file.SeekStart()
if err != nil {
_ = file.Close()
return nil, true
}
file.version = this.version
return file, true
default:
return nil, false
}
}
func (this *OpenFilePool) Put(file *OpenFile) bool {
if file.version > 0 && file.version != this.version {
_ = file.Close()
return false
}
select {
case this.c <- file:
return true
default:
// 多余的直接关闭
_ = file.Close()
return false
}
}
func (this *OpenFilePool) Len() int {
return len(this.c)
}
func (this *OpenFilePool) Close() {
Loop:
for {
select {
case file := <-this.c:
_ = file.Close()
default:
break Loop
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"testing"
)
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, []byte{})))
t.Log(pool.Get())
t.Log(pool.Get())
}

View File

@@ -0,0 +1,143 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
import (
"encoding/json"
)
// PartialRanges 内容分区范围定义
type PartialRanges struct {
ranges [][2]int64
}
// NewPartialRanges 获取新对象
func NewPartialRanges() *PartialRanges {
return &PartialRanges{ranges: [][2]int64{}}
}
// NewPartialRangesFromJSON 从JSON中解析范围
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
var rs = [][2]int64{}
err := json.Unmarshal(data, &rs)
if err != nil {
return nil, err
}
var r = NewPartialRanges()
r.ranges = rs
return r, nil
}
// Add 添加新范围
func (this *PartialRanges) Add(begin int64, end int64) {
if begin > end {
begin, end = end, begin
}
var nr = [2]int64{begin, end}
var count = len(this.ranges)
if count == 0 {
this.ranges = [][2]int64{nr}
return
}
// insert
// TODO 将来使用二分法改进
var index = -1
for i, r := range this.ranges {
if r[0] > begin || (r[0] == begin && r[1] >= end) {
index = i
this.ranges = append(this.ranges, [2]int64{})
copy(this.ranges[index+1:], this.ranges[index:])
this.ranges[index] = nr
break
}
}
if index == -1 {
index = count
this.ranges = append(this.ranges, nr)
}
this.merge(index)
}
// Ranges 获取所有范围
func (this *PartialRanges) Ranges() [][2]int64 {
return this.ranges
}
// Contains 检查是否包含某个范围
func (this *PartialRanges) Contains(begin int64, end int64) bool {
if len(this.ranges) == 0 {
return true
}
// TODO 使用二分法查找改进性能
for _, r2 := range this.ranges {
if r2[0] <= begin && r2[1] >= end {
return true
}
}
return false
}
// AsJSON 转换为JSON
func (this *PartialRanges) AsJSON() ([]byte, error) {
return json.Marshal(this.ranges)
}
func (this *PartialRanges) merge(index int) {
// forward
var lastIndex = index
for i := index; i >= 1; i-- {
var curr = this.ranges[i]
var prev = this.ranges[i-1]
var w1 = this.w(curr)
var w2 = this.w(prev)
if w1+w2 >= this.max(curr[1], prev[1])-this.min(curr[0], prev[0])-1 {
prev = [2]int64{this.min(curr[0], prev[0]), this.max(curr[1], prev[1])}
this.ranges[i-1] = prev
this.ranges = append(this.ranges[:i], this.ranges[i+1:]...)
lastIndex = i - 1
} else {
break
}
}
// backward
index = lastIndex
for index < len(this.ranges)-1 {
var curr = this.ranges[index]
var next = this.ranges[index+1]
var w1 = this.w(curr)
var w2 = this.w(next)
if w1+w2 >= this.max(curr[1], next[1])-this.min(curr[0], next[0])-1 {
curr = [2]int64{this.min(curr[0], next[0]), this.max(curr[1], next[1])}
this.ranges = append(this.ranges[:index], this.ranges[index+1:]...)
this.ranges[index] = curr
} else {
break
}
}
}
func (this *PartialRanges) w(r [2]int64) int64 {
return r[1] - r[0]
}
func (this *PartialRanges) min(n1 int64, n2 int64) int64 {
if n1 <= n2 {
return n1
}
return n2
}
func (this *PartialRanges) max(n1 int64, n2 int64) int64 {
if n1 >= n2 {
return n1
}
return n2
}

View File

@@ -0,0 +1,124 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestNewPartialRanges(t *testing.T) {
var r = caches.NewPartialRanges()
r.Add(1, 100)
r.Add(50, 300)
r.Add(30, 80)
r.Add(30, 100)
r.Add(30, 400)
r.Add(1000, 10000)
r.Add(200, 1000)
r.Add(200, 10040)
logs.PrintAsJSON(r.Ranges())
}
func TestNewPartialRanges1(t *testing.T) {
var a = assert.NewAssertion(t)
var r = caches.NewPartialRanges()
r.Add(1, 100)
r.Add(1, 101)
r.Add(1, 102)
r.Add(2, 103)
r.Add(200, 300)
r.Add(1, 1000)
var rs = r.Ranges()
logs.PrintAsJSON(rs, t)
a.IsTrue(len(rs) == 1)
if len(rs) == 1 {
a.IsTrue(rs[0][0] == 1)
a.IsTrue(rs[0][1] == 1000)
}
}
func TestNewPartialRanges2(t *testing.T) {
// low -> high
var r = caches.NewPartialRanges()
r.Add(1, 100)
r.Add(1, 101)
r.Add(1, 102)
r.Add(2, 103)
r.Add(200, 300)
r.Add(301, 302)
r.Add(303, 304)
r.Add(250, 400)
var rs = r.Ranges()
logs.PrintAsJSON(rs, t)
}
func TestNewPartialRanges3(t *testing.T) {
// high -> low
var r = caches.NewPartialRanges()
r.Add(301, 302)
r.Add(303, 304)
r.Add(200, 300)
r.Add(250, 400)
var rs = r.Ranges()
logs.PrintAsJSON(rs, t)
}
func TestNewPartialRanges4(t *testing.T) {
// nearby
var r = caches.NewPartialRanges()
r.Add(301, 302)
r.Add(303, 304)
r.Add(305, 306)
r.Add(417, 417)
r.Add(410, 415)
r.Add(400, 409)
var rs = r.Ranges()
logs.PrintAsJSON(rs, t)
t.Log(r.Contains(400, 416))
}
func TestNewPartialRanges5(t *testing.T) {
var r = caches.NewPartialRanges()
for j := 0; j < 1000; j++ {
r.Add(int64(j), int64(j+100))
}
logs.PrintAsJSON(r.Ranges(), t)
}
func TestNewPartialRanges_AsJSON(t *testing.T) {
var r = caches.NewPartialRanges()
for j := 0; j < 1000; j++ {
r.Add(int64(j), int64(j+100))
}
data, err := r.AsJSON()
if err != nil {
t.Fatal(err)
}
t.Log(string(data))
r2, err := caches.NewPartialRangesFromJSON(data)
if err != nil {
t.Fatal(err)
}
t.Log(r2.Ranges())
}
func BenchmarkNewPartialRanges(b *testing.B) {
for i := 0; i < b.N; i++ {
var r = caches.NewPartialRanges()
for j := 0; j < 1000; j++ {
r.Add(int64(j), int64(j+100))
}
}
}

View File

@@ -24,6 +24,9 @@ type Reader interface {
// ReadBody 读取Body // ReadBody 读取Body
ReadBody(buf []byte, callback ReaderFunc) error ReadBody(buf []byte, callback ReaderFunc) error
// Read 实现io.Reader接口
Read(buf []byte) (int, error)
// ReadBodyRange 读取某个范围内的Body // ReadBodyRange 读取某个范围内的Body
ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error

View File

@@ -11,6 +11,12 @@ import (
type FileReader struct { type FileReader struct {
fp *os.File fp *os.File
openFile *OpenFile
openFileCache *OpenFileCache
meta []byte
header []byte
expiresAt int64 expiresAt int64
status int status int
headerOffset int64 headerOffset int64
@@ -18,8 +24,7 @@ type FileReader struct {
bodySize int64 bodySize int64
bodyOffset int64 bodyOffset int64
bodyBufLen int isClosed bool
bodyBuf []byte
} }
func NewFileReader(fp *os.File) *FileReader { func NewFileReader(fp *os.File) *FileReader {
@@ -27,21 +32,36 @@ func NewFileReader(fp *os.File) *FileReader {
} }
func (this *FileReader) Init() error { func (this *FileReader) Init() error {
return this.InitAutoDiscard(true)
}
func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
if this.openFile != nil {
this.meta = this.openFile.meta
this.header = this.openFile.header
}
isOk := false isOk := false
defer func() { if autoDiscard {
if !isOk { defer func() {
_ = this.discard() if !isOk {
} _ = this.discard()
}() }
}()
var buf = make([]byte, SizeMeta)
ok, err := this.readToBuff(this.fp, buf)
if err != nil {
return err
} }
if !ok {
return ErrNotFound var buf = this.meta
if len(buf) == 0 {
buf = make([]byte, SizeMeta)
ok, err := this.readToBuff(this.fp, buf)
if err != nil {
return err
}
if !ok {
return ErrNotFound
}
this.meta = buf
} }
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt])) this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
@@ -64,13 +84,28 @@ func (this *FileReader) Init() error {
this.headerOffset = int64(SizeMeta) + int64(urlLength) this.headerOffset = int64(SizeMeta) + int64(urlLength)
// body // body
this.bodyOffset = this.headerOffset + int64(headerSize)
bodySize := int(binary.BigEndian.Uint64(buf[SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength+SizeBodyLength])) bodySize := int(binary.BigEndian.Uint64(buf[SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength+SizeBodyLength]))
if bodySize == 0 { if bodySize == 0 {
isOk = true isOk = true
return nil return nil
} }
this.bodySize = int64(bodySize) this.bodySize = int64(bodySize)
this.bodyOffset = this.headerOffset + int64(headerSize)
// read header
if this.openFileCache != nil && len(this.header) == 0 {
if headerSize > 0 && headerSize <= 512 {
this.header = make([]byte, headerSize)
_, err := this.fp.Seek(this.headerOffset, io.SeekStart)
if err != nil {
return err
}
_, err = this.readToBuff(this.fp, this.header)
if err != nil {
return err
}
}
}
isOk = true isOk = true
@@ -106,6 +141,22 @@ func (this *FileReader) BodySize() int64 {
} }
func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error { func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
// 使用缓存
if len(this.header) > 0 && len(buf) >= len(this.header) {
copy(buf, this.header)
_, err := callback(len(this.header))
if err != nil {
return err
}
// 移动到Body位置
_, err = this.fp.Seek(this.bodyOffset, io.SeekStart)
if err != nil {
return err
}
return nil
}
isOk := false isOk := false
defer func() { defer func() {
@@ -135,10 +186,6 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
} }
headerSize -= n headerSize -= n
} else { } else {
if n > headerSize {
this.bodyBuf = buf[headerSize:]
this.bodyBufLen = n - headerSize
}
_, e := callback(headerSize) _, e := callback(headerSize)
if e != nil { if e != nil {
isOk = true isOk = true
@@ -157,6 +204,12 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
isOk = true isOk = true
// 移动到Body位置
_, err = this.fp.Seek(this.bodyOffset, io.SeekStart)
if err != nil {
return err
}
return nil return nil
} }
@@ -169,27 +222,7 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
} }
}() }()
offset := this.bodyOffset var offset = this.bodyOffset
// 直接返回从Header中剩余的
if this.bodyBufLen > 0 && len(buf) >= this.bodyBufLen {
offset += int64(this.bodyBufLen)
copy(buf, this.bodyBuf)
isOk = true
goNext, err := callback(this.bodyBufLen)
if err != nil {
return err
}
if !goNext {
return nil
}
if this.bodySize <= int64(this.bodyBufLen) {
return nil
}
}
// 开始读Body部分 // 开始读Body部分
_, err := this.fp.Seek(offset, io.SeekStart) _, err := this.fp.Seek(offset, io.SeekStart)
@@ -222,6 +255,14 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
return nil return nil
} }
func (this *FileReader) Read(buf []byte) (n int, err error) {
n, err = this.fp.Read(buf)
if err != nil && err != io.EOF {
_ = this.discard()
}
return
}
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error { func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
isOk := false isOk := false
@@ -292,6 +333,19 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
} }
func (this *FileReader) Close() error { func (this *FileReader) Close() error {
if this.openFileCache != nil {
if this.isClosed {
return nil
}
this.isClosed = true
if this.openFile != nil {
this.openFileCache.Put(this.fp.Name(), this.openFile)
} else {
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, this.meta, this.header))
}
return nil
}
return this.fp.Close() return this.fp.Close()
} }
@@ -306,5 +360,6 @@ func (this *FileReader) readToBuff(fp *os.File, buf []byte) (ok bool, err error)
func (this *FileReader) discard() error { func (this *FileReader) discard() error {
_ = this.fp.Close() _ = this.fp.Close()
this.isClosed = true
return os.Remove(this.fp.Name()) return os.Remove(this.fp.Name())
} }

View File

@@ -54,6 +54,30 @@ func TestFileReader(t *testing.T) {
} }
} }
func TestFileReader_ReadHeader(t *testing.T) {
var path = "/Users/WorkSpace/EdgeProject/EdgeCache/p43/12/6b/126bbed90fc80f2bdfb19558948b0d49.cache"
fp, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer func() {
_ = fp.Close()
}()
var reader = NewFileReader(fp)
err = reader.Init()
if err != nil {
t.Fatal(err)
}
var buf = make([]byte, 16*1024)
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) {
t.Log("header:", string(buf[:n]))
return
})
if err != nil {
t.Fatal(err)
}
}
func TestFileReader_Range(t *testing.T) { func TestFileReader_Range(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{ storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1, Id: 1,

View File

@@ -2,10 +2,13 @@ package caches
import ( import (
"errors" "errors"
"io"
) )
type MemoryReader struct { type MemoryReader struct {
item *MemoryItem item *MemoryItem
offset int
} }
func NewMemoryReader(item *MemoryItem) *MemoryReader { func NewMemoryReader(item *MemoryItem) *MemoryReader {
@@ -111,6 +114,33 @@ func (this *MemoryReader) ReadBody(buf []byte, callback ReaderFunc) error {
return nil return nil
} }
func (this *MemoryReader) Read(buf []byte) (n int, err error) {
bufLen := len(buf)
if bufLen == 0 {
return 0, errors.New("using empty buffer")
}
bodySize := len(this.item.BodyValue)
left := bodySize - this.offset
if bufLen <= left {
copy(buf, this.item.BodyValue[this.offset:this.offset+bufLen])
n = bufLen
this.offset += bufLen
if this.offset >= bodySize {
err = io.EOF
return
}
return
} else {
copy(buf, this.item.BodyValue[this.offset:])
n = left
err = io.EOF
return
}
}
func (this *MemoryReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error { func (this *MemoryReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
offset := start offset := start
bodySize := int64(len(this.item.BodyValue)) bodySize := int64(len(this.item.BodyValue))

View File

@@ -8,16 +8,18 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/trackers" "github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string" stringutil "github.com/iwind/TeaGo/utils/string"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
"io"
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
@@ -45,6 +47,9 @@ const (
HotItemSize = 1024 HotItemSize = 1024
) )
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
var sharedWritingFileKeyLocker = sync.Mutex{}
// FileStorage 文件缓存 // FileStorage 文件缓存
// 文件结构: // 文件结构:
// [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data] // [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data]
@@ -54,23 +59,23 @@ type FileStorage struct {
memoryStorage *MemoryStorage // 一级缓存 memoryStorage *MemoryStorage // 一级缓存
totalSize int64 totalSize int64
list ListInterface list ListInterface
writingKeyMap map[string]bool // key => bool locker sync.RWMutex
locker sync.RWMutex purgeTicker *utils.Ticker
purgeTicker *utils.Ticker
hotMap map[string]*HotItem // key => count hotMap map[string]*HotItem // key => count
hotMapLocker sync.Mutex hotMapLocker sync.Mutex
lastHotSize int lastHotSize int
hotTicker *utils.Ticker hotTicker *utils.Ticker
openFileCache *OpenFileCache
} }
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage { func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
return &FileStorage{ return &FileStorage{
policy: policy, policy: policy,
writingKeyMap: map[string]bool{}, hotMap: map[string]*HotItem{},
hotMap: map[string]*HotItem{}, lastHotSize: -1,
lastHotSize: -1,
} }
} }
@@ -97,13 +102,13 @@ func (this *FileStorage) Init() error {
return err return err
} }
this.cacheConfig = cacheConfig this.cacheConfig = cacheConfig
cacheDir := cacheConfig.Dir
if !filepath.IsAbs(this.cacheConfig.Dir) { if !filepath.IsAbs(this.cacheConfig.Dir) {
this.cacheConfig.Dir = Tea.Root + Tea.DS + this.cacheConfig.Dir this.cacheConfig.Dir = Tea.Root + Tea.DS + this.cacheConfig.Dir
} }
dir := this.cacheConfig.Dir this.cacheConfig.Dir = filepath.Clean(this.cacheConfig.Dir)
var dir = this.cacheConfig.Dir
if len(dir) == 0 { if len(dir) == 0 {
return errors.New("[CACHE]cache storage dir can not be empty") return errors.New("[CACHE]cache storage dir can not be empty")
@@ -156,7 +161,7 @@ func (this *FileStorage) Init() error {
} else if size > 1024 { } else if size > 1024 {
sizeMB = fmt.Sprintf("%.3f K", float64(size)/1024) sizeMB = fmt.Sprintf("%.3f K", float64(size)/1024)
} }
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+cacheDir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB) remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.cacheConfig.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
}() }()
// 初始化list // 初始化list
@@ -199,17 +204,31 @@ func (this *FileStorage) Init() error {
} }
} }
// open file cache
if this.cacheConfig.OpenFileCache != nil && this.cacheConfig.OpenFileCache.IsOn && this.cacheConfig.OpenFileCache.Max > 0 {
this.openFileCache, err = NewOpenFileCache(this.cacheConfig.OpenFileCache.Max)
logs.Println("start open file cache")
if err != nil {
remotelogs.Error("CACHE", "open file cache failed: "+err.Error())
}
}
return nil return nil
} }
func (this *FileStorage) OpenReader(key string) (Reader, error) { func (this *FileStorage) OpenReader(key string, useStale bool) (Reader, error) {
return this.openReader(key, true) return this.openReader(key, true, useStale)
} }
func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error) { func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool) (Reader, error) {
// 使用陈旧缓存的时候,我们认为是短暂的,只需要从文件里检查即可
if useStale {
allowMemory = false
}
// 先尝试内存缓存 // 先尝试内存缓存
if allowMemory && this.memoryStorage != nil { if allowMemory && this.memoryStorage != nil {
reader, err := this.memoryStorage.OpenReader(key) reader, err := this.memoryStorage.OpenReader(key, useStale)
if err == nil { if err == nil {
return reader, err return reader, err
} }
@@ -217,14 +236,35 @@ func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error
hash, path := this.keyPath(key) hash, path := this.keyPath(key)
// TODO 尝试使用mmap加快读取速度 // 检查文件记录是否已过期
var isOk = false if !useStale {
fp, err := os.OpenFile(path, os.O_RDONLY, 0444) exists, err := this.list.Exist(hash)
if err != nil { if err != nil {
if !os.IsNotExist(err) {
return nil, err return nil, err
} }
return nil, ErrNotFound if !exists {
return nil, ErrNotFound
}
}
// TODO 尝试使用mmap加快读取速度
var isOk = false
var openFile *OpenFile
if this.openFileCache != nil {
openFile = this.openFileCache.Get(path)
}
var fp *os.File
var err error
if openFile == nil {
fp, err = os.OpenFile(path, os.O_RDONLY, 0444)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
return nil, ErrNotFound
}
} else {
fp = openFile.fp
} }
defer func() { defer func() {
if !isOk { if !isOk {
@@ -233,19 +273,9 @@ func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error
} }
}() }()
// 检查文件记录是否已过期 var reader = NewFileReader(fp)
exists, err := this.list.Exist(hash) reader.openFile = openFile
if err != nil { reader.openFileCache = this.openFileCache
return nil, err
}
if !exists {
return nil, ErrNotFound
}
reader := NewFileReader(fp)
if err != nil {
return nil, err
}
err = reader.Init() err = reader.Init()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -295,31 +325,31 @@ func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error
} }
// OpenWriter 打开缓存文件等待写入 // OpenWriter 打开缓存文件等待写入
func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Writer, error) { func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, size int64, isPartial bool) (Writer, error) {
// 先尝试内存缓存 // 先尝试内存缓存
if this.memoryStorage != nil { // 我们限定仅小文件优先存在内存中
writer, err := this.memoryStorage.OpenWriter(key, expiredAt, status) if !isPartial && this.memoryStorage != nil && size > 0 && size < 32*1024*1024 {
writer, err := this.memoryStorage.OpenWriter(key, expiredAt, status, size, false)
if err == nil { if err == nil {
return writer, nil return writer, nil
} }
} }
// 是否正在写入 // 是否正在写入
var isWriting = false var isOk = false
this.locker.Lock() sharedWritingFileKeyLocker.Lock()
_, ok := this.writingKeyMap[key] _, ok := sharedWritingFileKeyMap[key]
this.locker.Unlock()
if ok { if ok {
sharedWritingFileKeyLocker.Unlock()
return nil, ErrFileIsWriting return nil, ErrFileIsWriting
} }
this.locker.Lock() sharedWritingFileKeyMap[key] = zero.New()
this.writingKeyMap[key] = true sharedWritingFileKeyLocker.Unlock()
this.locker.Unlock()
defer func() { defer func() {
if !isWriting { if !isOk {
this.locker.Lock() sharedWritingFileKeyLocker.Lock()
delete(this.writingKeyMap, key) delete(sharedWritingFileKeyMap, key)
this.locker.Unlock() sharedWritingFileKeyLocker.Unlock()
} }
}() }()
@@ -331,13 +361,13 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys { if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys {
return nil, NewCapacityError("write file cache failed: too many keys in cache storage") return nil, NewCapacityError("write file cache failed: too many keys in cache storage")
} }
capacityBytes := this.diskCapacityBytes() var capacityBytes = this.diskCapacityBytes()
if capacityBytes > 0 && capacityBytes <= this.totalSize { if capacityBytes > 0 && capacityBytes <= this.totalSize {
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10)) return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10))
} }
hash := stringutil.Md5(key) var hash = stringutil.Md5(key)
dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4] var dir = this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
_, err = os.Stat(dir) _, err = os.Stat(dir)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
@@ -349,21 +379,30 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
} }
} }
// 检查缓存是否已经生成
var cachePath = dir + "/" + hash + ".cache"
stat, err := os.Stat(cachePath)
if err == nil && time.Now().Sub(stat.ModTime()) <= 1*time.Second {
// 防止并发连续写入
return nil, ErrFileIsWriting
}
var tmpPath = cachePath + ".tmp"
if isPartial {
tmpPath = cachePath
}
// 先删除 // 先删除
err = this.list.Remove(hash) err = this.list.Remove(hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
path := dir + "/" + hash + ".cache.tmp" writer, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_SYNC|os.O_WRONLY, 0666)
writer, err := os.OpenFile(path, os.O_CREATE|os.O_SYNC|os.O_WRONLY, 0666)
if err != nil { if err != nil {
return nil, err return nil, err
} }
isWriting = true
isOk := false var removeOnFailure = true
removeOnFailure := true
defer func() { defer func() {
if err != nil { if err != nil {
isOk = false isOk = false
@@ -373,7 +412,7 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
if !isOk { if !isOk {
_ = writer.Close() _ = writer.Close()
if removeOnFailure { if removeOnFailure {
_ = os.Remove(path) _ = os.Remove(tmpPath)
} }
} }
}() }()
@@ -385,71 +424,96 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
return nil, ErrFileIsWriting return nil, ErrFileIsWriting
} }
err = writer.Truncate(0) // 是否已经有内容
if err != nil { var isNewCreated = true
return nil, err var partialBodyOffset int64
} if isPartial {
partialFP, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
// 写入过期时间 if err == nil {
bytes4 := make([]byte, 4) var partialReader = NewFileReader(partialFP)
{ err = partialReader.InitAutoDiscard(false)
binary.BigEndian.PutUint32(bytes4, uint32(expiredAt)) if err == nil && partialReader.bodyOffset > 0 {
_, err = writer.Write(bytes4) isNewCreated = false
if err != nil { partialBodyOffset = partialReader.bodyOffset
return nil, err }
_ = partialReader.Close()
} }
} }
// 写入状态码 if isNewCreated {
if status > 999 || status < 100 { err = writer.Truncate(0)
status = 200
}
_, err = writer.WriteString(strconv.Itoa(status))
if err != nil {
return nil, err
}
// 写入URL长度
{
binary.BigEndian.PutUint32(bytes4, uint32(len(key)))
_, err = writer.Write(bytes4)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
// 写入Header Length // 写入过期时间
{ bytes4 := make([]byte, 4)
binary.BigEndian.PutUint32(bytes4, uint32(0)) {
_, err = writer.Write(bytes4) binary.BigEndian.PutUint32(bytes4, uint32(expiredAt))
_, err = writer.Write(bytes4)
if err != nil {
return nil, err
}
}
// 写入状态码
if status > 999 || status < 100 {
status = 200
}
_, err = writer.WriteString(strconv.Itoa(status))
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
// 写入Body Length // 写入URL长度
{ {
b := make([]byte, SizeBodyLength) binary.BigEndian.PutUint32(bytes4, uint32(len(key)))
binary.BigEndian.PutUint64(b, uint64(0)) _, err = writer.Write(bytes4)
_, err = writer.Write(b) if err != nil {
return nil, err
}
}
// 写入Header Length
{
binary.BigEndian.PutUint32(bytes4, uint32(0))
_, err = writer.Write(bytes4)
if err != nil {
return nil, err
}
}
// 写入Body Length
{
b := make([]byte, SizeBodyLength)
binary.BigEndian.PutUint64(b, uint64(0))
_, err = writer.Write(b)
if err != nil {
return nil, err
}
}
// 写入URL
_, err = writer.WriteString(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
// 写入URL
_, err = writer.WriteString(key)
if err != nil {
return nil, err
}
isOk = true isOk = true
if isPartial {
return NewFileWriter(writer, key, expiredAt, func() { return NewPartialFileWriter(writer, key, expiredAt, isNewCreated, isPartial, partialBodyOffset, func() {
this.locker.Lock() sharedWritingFileKeyLocker.Lock()
delete(this.writingKeyMap, key) delete(sharedWritingFileKeyMap, key)
this.locker.Unlock() sharedWritingFileKeyLocker.Unlock()
}), nil }), nil
} else {
return NewFileWriter(writer, key, expiredAt, func() {
sharedWritingFileKeyLocker.Lock()
delete(sharedWritingFileKeyMap, key)
sharedWritingFileKeyLocker.Unlock()
}), nil
}
} }
// AddToList 添加到List // AddToList 添加到List
@@ -562,12 +626,12 @@ func (this *FileStorage) CleanAll() error {
} }
// 重新遍历待删除 // 重新遍历待删除
go func() { goman.New(func() {
err = this.cleanDeletedDirs(dir) err = this.cleanDeletedDirs(dir)
if err != nil { if err != nil {
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error()) remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
} }
}() })
return nil return nil
} }
@@ -609,6 +673,8 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
// Stop 停止 // Stop 停止
func (this *FileStorage) Stop() { func (this *FileStorage) Stop() {
events.Remove(this)
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
@@ -626,6 +692,10 @@ func (this *FileStorage) Stop() {
} }
_ = this.list.Close() _ = this.list.Close()
if this.openFileCache != nil {
this.openFileCache.CloseAll()
}
} }
// TotalDiskSize 消耗的磁盘尺寸 // TotalDiskSize 消耗的磁盘尺寸
@@ -672,12 +742,12 @@ func (this *FileStorage) initList() error {
} }
// 使用异步防止阻塞主线程 // 使用异步防止阻塞主线程
/**go func() { /**goman.New(func() {
dir := this.dir() dir := this.dir()
// 清除tmp // 清除tmp
// TODO 需要一个更加高效的实现 // TODO 需要一个更加高效的实现
}()**/ })**/
// 启动定时清理任务 // 启动定时清理任务
var autoPurgeInterval = this.policy.PersistenceAutoPurgeInterval var autoPurgeInterval = this.policy.PersistenceAutoPurgeInterval
@@ -688,129 +758,46 @@ func (this *FileStorage) initList() error {
} }
} }
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second) this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
events.On(events.EventQuit, func() { events.OnKey(events.EventQuit, this, func() {
remotelogs.Println("CACHE", "quit clean timer") remotelogs.Println("CACHE", "quit clean timer")
var ticker = this.purgeTicker
if ticker != nil { {
ticker.Stop() var ticker = this.purgeTicker
if ticker != nil {
ticker.Stop()
}
}
{
var ticker = this.hotTicker
if ticker != nil {
ticker.Stop()
}
} }
}) })
go func() { goman.New(func() {
for this.purgeTicker.Next() { for this.purgeTicker.Next() {
trackers.Run("FILE_CACHE_STORAGE_PURGE_LOOP", func() { trackers.Run("FILE_CACHE_STORAGE_PURGE_LOOP", func() {
this.purgeLoop() this.purgeLoop()
}) })
} }
}() })
// 热点处理任务 // 热点处理任务
this.hotTicker = utils.NewTicker(1 * time.Minute) this.hotTicker = utils.NewTicker(1 * time.Minute)
if Tea.IsTesting() { if Tea.IsTesting() {
this.hotTicker = utils.NewTicker(10 * time.Second) this.hotTicker = utils.NewTicker(10 * time.Second)
} }
go func() { goman.New(func() {
for this.hotTicker.Next() { for this.hotTicker.Next() {
trackers.Run("FILE_CACHE_STORAGE_HOT_LOOP", func() { trackers.Run("FILE_CACHE_STORAGE_HOT_LOOP", func() {
this.hotLoop() this.hotLoop()
}) })
} }
}() })
return nil return nil
} }
// 解析文件信息
func (this *FileStorage) decodeFile(path string) (*Item, error) {
fp, err := os.OpenFile(path, os.O_RDONLY, 0444)
if err != nil {
return nil, err
}
isAllOk := false
defer func() {
_ = fp.Close()
if !isAllOk {
_ = os.Remove(path)
}
}()
item := &Item{
Type: ItemTypeFile,
MetaSize: SizeMeta,
}
bytes4 := make([]byte, 4)
// 过期时间
ok, err := this.readToBuff(fp, bytes4)
if err != nil {
return nil, err
}
if !ok {
return nil, ErrNotFound
}
item.ExpiredAt = int64(binary.BigEndian.Uint32(bytes4))
// 是否已过期
if item.ExpiredAt < time.Now().Unix() {
return nil, ErrNotFound
}
// URL Size
_, err = fp.Seek(int64(SizeExpiresAt+SizeStatus), io.SeekStart)
if err != nil {
return nil, err
}
ok, err = this.readToBuff(fp, bytes4)
if err != nil {
return nil, err
}
if !ok {
return nil, ErrNotFound
}
urlSize := binary.BigEndian.Uint32(bytes4)
// Header Size
ok, err = this.readToBuff(fp, bytes4)
if err != nil {
return nil, err
}
if !ok {
return nil, ErrNotFound
}
item.HeaderSize = int64(binary.BigEndian.Uint32(bytes4))
// Body Size
bytes8 := make([]byte, 8)
ok, err = this.readToBuff(fp, bytes8)
if err != nil {
return nil, err
}
if !ok {
return nil, ErrNotFound
}
item.BodySize = int64(binary.BigEndian.Uint64(bytes8))
// URL
if urlSize > 0 {
data := utils.BytePool1024.Get()
result, ok, err := this.readN(fp, data, int(urlSize))
utils.BytePool1024.Put(data)
if err != nil {
return nil, err
}
if !ok {
return nil, ErrNotFound
}
item.Key = string(result)
}
isAllOk = true
return item, nil
}
// 清理任务 // 清理任务
func (this *FileStorage) purgeLoop() { func (this *FileStorage) purgeLoop() {
// 计算是否应该开启LFU清理 // 计算是否应该开启LFU清理
@@ -921,7 +908,7 @@ func (this *FileStorage) hotLoop() {
this.hotMap = map[string]*HotItem{} this.hotMap = map[string]*HotItem{}
this.hotMapLocker.Unlock() this.hotMapLocker.Unlock()
// 取Top10 // 取Top10写入内存
if len(result) > 0 { if len(result) > 0 {
sort.Slice(result, func(i, j int) bool { sort.Slice(result, func(i, j int) bool {
return result[i].Hits > result[j].Hits return result[i].Hits > result[j].Hits
@@ -933,9 +920,10 @@ func (this *FileStorage) hotLoop() {
size = len(result) / 10 size = len(result) / 10
} }
var buf = make([]byte, 32*1024) var buf = utils.BytePool16k.Get()
defer utils.BytePool16k.Put(buf)
for _, item := range result[:size] { for _, item := range result[:size] {
reader, err := this.openReader(item.Key, false) reader, err := this.openReader(item.Key, false, false)
if err != nil { if err != nil {
continue continue
} }
@@ -946,7 +934,7 @@ func (this *FileStorage) hotLoop() {
continue continue
} }
writer, err := this.memoryStorage.openWriter(item.Key, item.ExpiresAt, item.Status, false) writer, err := this.memoryStorage.openWriter(item.Key, item.ExpiresAt, item.Status, reader.BodySize(), false)
if err != nil { if err != nil {
if !CanIgnoreErr(err) { if !CanIgnoreErr(err) {
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error()) remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
@@ -971,6 +959,9 @@ func (this *FileStorage) hotLoop() {
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) { err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
_, err = writer.Write(buf[:n]) _, err = writer.Write(buf[:n])
if err == nil {
goNext = true
}
return return
}) })
if err != nil { if err != nil {
@@ -993,34 +984,6 @@ func (this *FileStorage) hotLoop() {
} }
} }
func (this *FileStorage) readToBuff(fp *os.File, buf []byte) (ok bool, err error) {
n, err := fp.Read(buf)
if err != nil {
return false, err
}
ok = n == len(buf)
return
}
func (this *FileStorage) readN(fp *os.File, buf []byte, total int) (result []byte, ok bool, err error) {
for {
n, err := fp.Read(buf)
if err != nil {
return nil, false, err
}
if n > 0 {
if n >= total {
result = append(result, buf[:total]...)
ok = true
return result, ok, nil
} else {
total -= n
result = append(result, buf[:n]...)
}
}
}
}
func (this *FileStorage) diskCapacityBytes() int64 { func (this *FileStorage) diskCapacityBytes() int64 {
c1 := this.policy.CapacityBytes() c1 := this.policy.CapacityBytes()
if SharedManager.MaxDiskCapacity != nil { if SharedManager.MaxDiskCapacity != nil {

View File

@@ -62,7 +62,7 @@ func TestFileStorage_OpenWriter(t *testing.T) {
header := []byte("Header") header := []byte("Header")
body := []byte("This is Body") body := []byte("This is Body")
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200) writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -87,6 +87,41 @@ func TestFileStorage_OpenWriter(t *testing.T) {
t.Log("ok") t.Log("ok")
} }
func TestFileStorage_OpenWriter_Partial(t *testing.T) {
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 2,
IsOn: true,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
})
err := storage.Init()
if err != nil {
t.Fatal(err)
}
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, true)
if err != nil {
t.Fatal(err)
}
_, err = writer.WriteHeader([]byte("Content-Type:text/html; charset=utf-8"))
if err != nil {
t.Fatal(err)
}
err = writer.WriteAt([]byte("Hello, World"), 0)
if err != nil {
t.Fatal(err)
}
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log(writer)
}
func TestFileStorage_OpenWriter_HTTP(t *testing.T) { func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{ storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1, Id: 1,
@@ -104,7 +139,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
t.Log(time.Since(now).Seconds()*1000, "ms") t.Log(time.Since(now).Seconds()*1000, "ms")
}() }()
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200) writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -177,10 +212,11 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200) writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, false)
if err != nil { if err != nil {
if err != ErrFileIsWriting { if err != ErrFileIsWriting {
t.Fatal(err) t.Error(err)
return
} }
return return
} }
@@ -188,7 +224,8 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
_, err = writer.Write([]byte("Hello,World")) _, err = writer.Write([]byte("Hello,World"))
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
// 故意造成慢速写入 // 故意造成慢速写入
@@ -196,7 +233,8 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
err = writer.Close() err = writer.Close()
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
}(i) }(i)
} }
@@ -229,10 +267,11 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200) writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, false)
if err != nil { if err != nil {
if err != ErrFileIsWriting { if err != ErrFileIsWriting {
t.Fatal(err) t.Error(err)
return
} }
return return
} }
@@ -241,7 +280,8 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
t.Log("writing") t.Log("writing")
_, err = writer.Write([]byte("Hello,World")) _, err = writer.Write([]byte("Hello,World"))
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
// 故意造成慢速写入 // 故意造成慢速写入
@@ -249,7 +289,8 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
err = writer.Close() err = writer.Close()
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
return
} }
}(i) }(i)
} }
@@ -270,7 +311,7 @@ func TestFileStorage_Read(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
now := time.Now() now := time.Now()
reader, err := storage.OpenReader("my-key") reader, err := storage.OpenReader("my-key", false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -306,7 +347,7 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
now := time.Now() now := time.Now()
reader, err := storage.OpenReader("my-http-response") reader, err := storage.OpenReader("my-http-response", false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -360,7 +401,7 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
} }
now := time.Now() now := time.Now()
buf := make([]byte, 6) buf := make([]byte, 6)
reader, err := storage.OpenReader("my-key-10000") reader, err := storage.OpenReader("my-key-10000", false)
if err != nil { if err != nil {
if err == ErrNotFound { if err == ErrNotFound {
t.Log("cache not fund") t.Log("cache not fund")
@@ -482,11 +523,7 @@ func TestFileStorage_DecodeFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, path := storage.keyPath("my-key") _, path := storage.keyPath("my-key")
item, err := storage.decodeFile(path) t.Log(path)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(item, t)
} }
func BenchmarkFileStorage_Read(b *testing.B) { func BenchmarkFileStorage_Read(b *testing.B) {
@@ -506,7 +543,7 @@ func BenchmarkFileStorage_Read(b *testing.B) {
b.Fatal(err) b.Fatal(err)
} }
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
reader, err := storage.OpenReader("my-key") reader, err := storage.OpenReader("my-key", false)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View File

@@ -10,10 +10,10 @@ type StorageInterface interface {
Init() error Init() error
// OpenReader 读取缓存 // OpenReader 读取缓存
OpenReader(key string) (Reader, error) OpenReader(key string, useStale bool) (reader Reader, err error)
// OpenWriter 打开缓存写入器等待写入 // OpenWriter 打开缓存写入器等待写入
OpenWriter(key string, expiredAt int64, status int) (Writer, error) OpenWriter(key string, expiredAt int64, status int, size int64, isPartial bool) (Writer, error)
// Delete 删除某个键值对应的缓存 // Delete 删除某个键值对应的缓存
Delete(key string) error Delete(key string) error

View File

@@ -3,9 +3,11 @@ package caches
import ( import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/trackers" "github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/cespare/xxhash" "github.com/cespare/xxhash"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -43,7 +45,7 @@ type MemoryStorage struct {
purgeTicker *utils.Ticker purgeTicker *utils.Ticker
totalSize int64 totalSize int64
writingKeyMap map[string]bool // key => bool writingKeyMap map[string]zero.Zero // key => bool
} }
func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage StorageInterface) *MemoryStorage { func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage StorageInterface) *MemoryStorage {
@@ -62,7 +64,7 @@ func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage Stora
locker: &sync.RWMutex{}, locker: &sync.RWMutex{},
valuesMap: map[uint64]*MemoryItem{}, valuesMap: map[uint64]*MemoryItem{},
dirtyChan: dirtyChan, dirtyChan: dirtyChan,
writingKeyMap: map[string]bool{}, writingKeyMap: map[string]zero.Zero{},
} }
} }
@@ -84,26 +86,26 @@ func (this *MemoryStorage) Init() error {
// 启动定时清理任务 // 启动定时清理任务
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second) this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
go func() { goman.New(func() {
for this.purgeTicker.Next() { for this.purgeTicker.Next() {
var tr = trackers.Begin("MEMORY_CACHE_STORAGE_PURGE_LOOP") var tr = trackers.Begin("MEMORY_CACHE_STORAGE_PURGE_LOOP")
this.purgeLoop() this.purgeLoop()
tr.End() tr.End()
} }
}() })
// 启动定时Flush memory to disk任务 // 启动定时Flush memory to disk任务
go func() { goman.New(func() {
for hash := range this.dirtyChan { for hash := range this.dirtyChan {
this.flushItem(hash) this.flushItem(hash)
} }
}() })
return nil return nil
} }
// OpenReader 读取缓存 // OpenReader 读取缓存
func (this *MemoryStorage) OpenReader(key string) (Reader, error) { func (this *MemoryStorage) OpenReader(key string, useStale bool) (Reader, error) {
hash := this.hash(key) hash := this.hash(key)
this.locker.RLock() this.locker.RLock()
@@ -113,7 +115,7 @@ func (this *MemoryStorage) OpenReader(key string) (Reader, error) {
return nil, ErrNotFound return nil, ErrNotFound
} }
if item.ExpiredAt > utils.UnixTime() { if useStale || (item.ExpiredAt > utils.UnixTime()) {
reader := NewMemoryReader(item) reader := NewMemoryReader(item)
err := reader.Init() err := reader.Init()
if err != nil { if err != nil {
@@ -143,11 +145,15 @@ func (this *MemoryStorage) OpenReader(key string) (Reader, error) {
} }
// OpenWriter 打开缓存写入器等待写入 // OpenWriter 打开缓存写入器等待写入
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int) (Writer, error) { func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, size int64, isPartial bool) (Writer, error) {
return this.openWriter(key, expiredAt, status, true) // TODO 内存缓存暂时不支持分块内容存储
if isPartial {
return nil, ErrFileIsWriting
}
return this.openWriter(key, expiredAt, status, size, true)
} }
func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, isDirty bool) (Writer, error) { func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, size int64, isDirty bool) (Writer, error) {
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
@@ -157,7 +163,7 @@ func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, i
if ok { if ok {
return nil, ErrFileIsWriting return nil, ErrFileIsWriting
} }
this.writingKeyMap[key] = true this.writingKeyMap[key] = zero.New()
defer func() { defer func() {
if !isWriting { if !isWriting {
delete(this.writingKeyMap, key) delete(this.writingKeyMap, key)
@@ -180,7 +186,10 @@ func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, i
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage") return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
} }
capacityBytes := this.memoryCapacityBytes() capacityBytes := this.memoryCapacityBytes()
if capacityBytes > 0 && capacityBytes <= this.totalSize { if size < 0 {
size = 0
}
if capacityBytes > 0 && capacityBytes <= this.totalSize+size {
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes") return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
} }
@@ -254,7 +263,7 @@ func (this *MemoryStorage) Stop() {
this.locker.Lock() this.locker.Lock()
this.valuesMap = map[uint64]*MemoryItem{} this.valuesMap = map[uint64]*MemoryItem{}
this.writingKeyMap = map[string]bool{} this.writingKeyMap = map[string]zero.Zero{}
_ = this.list.Reset() _ = this.list.Reset()
if this.purgeTicker != nil { if this.purgeTicker != nil {
this.purgeTicker.Stop() this.purgeTicker.Stop()
@@ -382,7 +391,7 @@ func (this *MemoryStorage) flushItem(key string) {
return return
} }
writer, err := this.parentStorage.OpenWriter(key, item.ExpiredAt, item.Status) writer, err := this.parentStorage.OpenWriter(key, item.ExpiredAt, item.Status, -1, false)
if err != nil { if err != nil {
if !CanIgnoreErr(err) { if !CanIgnoreErr(err) {
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error()) remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())

View File

@@ -15,7 +15,7 @@ import (
func TestMemoryStorage_OpenWriter(t *testing.T) { func TestMemoryStorage_OpenWriter(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200) writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -25,7 +25,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
t.Log(storage.valuesMap) t.Log(storage.valuesMap)
{ {
reader, err := storage.OpenReader("abc") reader, err := storage.OpenReader("abc", false)
if err != nil { if err != nil {
if err == ErrNotFound { if err == ErrNotFound {
t.Log("not found: abc") t.Log("not found: abc")
@@ -52,7 +52,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
} }
{ {
_, err := storage.OpenReader("abc 2") _, err := storage.OpenReader("abc 2", false)
if err != nil { if err != nil {
if err == ErrNotFound { if err == ErrNotFound {
t.Log("not found: abc2") t.Log("not found: abc2")
@@ -62,13 +62,13 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
} }
} }
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200) writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _ = writer.Write([]byte("Hello123")) _, _ = writer.Write([]byte("Hello123"))
{ {
reader, err := storage.OpenReader("abc") reader, err := storage.OpenReader("abc", false)
if err != nil { if err != nil {
if err == ErrNotFound { if err == ErrNotFound {
t.Log("not found: abc") t.Log("not found: abc")
@@ -97,13 +97,13 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
IsDone: true, IsDone: true,
}, },
} }
_, _ = storage.OpenReader("test") _, _ = storage.OpenReader("test", false)
} }
func TestMemoryStorage_Delete(t *testing.T) { func TestMemoryStorage_Delete(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
{ {
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200) writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -111,7 +111,7 @@ func TestMemoryStorage_Delete(t *testing.T) {
t.Log(len(storage.valuesMap)) t.Log(len(storage.valuesMap))
} }
{ {
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200) writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -126,7 +126,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60 expiredAt := time.Now().Unix() + 60
{ {
writer, err := storage.OpenWriter("abc", expiredAt, 200) writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -139,7 +139,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
}) })
} }
{ {
writer, err := storage.OpenWriter("abc1", expiredAt, 200) writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -163,7 +163,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60 expiredAt := time.Now().Unix() + 60
{ {
writer, err := storage.OpenWriter("abc", expiredAt, 200) writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -175,7 +175,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
}) })
} }
{ {
writer, err := storage.OpenWriter("abc1", expiredAt, 200) writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -198,7 +198,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil) storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
expiredAt := time.Now().Unix() + 60 expiredAt := time.Now().Unix() + 60
{ {
writer, err := storage.OpenWriter("abc", expiredAt, 200) writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -210,7 +210,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
}) })
} }
{ {
writer, err := storage.OpenWriter("abc1", expiredAt, 200) writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -241,7 +241,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60)) expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
key := "abc" + strconv.Itoa(i) key := "abc" + strconv.Itoa(i)
writer, err := storage.OpenWriter(key, expiredAt, 200) writer, err := storage.OpenWriter(key, expiredAt, 200, -1, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -8,6 +8,9 @@ type Writer interface {
// Write 写入Body数据 // Write 写入Body数据
Write(data []byte) (n int, err error) Write(data []byte) (n int, err error)
// WriteAt 在指定位置写入数据
WriteAt(data []byte, offset int64) error
// HeaderSize 写入的Header数据大小 // HeaderSize 写入的Header数据大小
HeaderSize() int64 HeaderSize() int64

View File

@@ -1,76 +0,0 @@
package caches
import (
"github.com/TeaOSLab/EdgeNode/internal/compressions"
)
type compressionWriter struct {
rawWriter Writer
writer compressions.Writer
key string
expiredAt int64
}
func NewCompressionWriter(gw Writer, cpWriter compressions.Writer, key string, expiredAt int64) Writer {
return &compressionWriter{
rawWriter: gw,
writer: cpWriter,
key: key,
expiredAt: expiredAt,
}
}
func (this *compressionWriter) WriteHeader(data []byte) (n int, err error) {
return this.writer.Write(data)
}
// WriteHeaderLength 写入Header长度数据
func (this *compressionWriter) WriteHeaderLength(headerLength int) error {
return nil
}
// WriteBodyLength 写入Body长度数据
func (this *compressionWriter) WriteBodyLength(bodyLength int64) error {
return nil
}
func (this *compressionWriter) Write(data []byte) (n int, err error) {
return this.writer.Write(data)
}
func (this *compressionWriter) Close() error {
err := this.writer.Close()
if err != nil {
return err
}
return this.rawWriter.Close()
}
func (this *compressionWriter) Discard() error {
err := this.writer.Close()
if err != nil {
return err
}
return this.rawWriter.Discard()
}
func (this *compressionWriter) Key() string {
return this.key
}
func (this *compressionWriter) ExpiredAt() int64 {
return this.expiredAt
}
func (this *compressionWriter) HeaderSize() int64 {
return this.rawWriter.HeaderSize()
}
func (this *compressionWriter) BodySize() int64 {
return this.rawWriter.BodySize()
}
// ItemType 内容类型
func (this *compressionWriter) ItemType() ItemType {
return this.rawWriter.ItemType()
}

View File

@@ -2,10 +2,12 @@ package caches
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"io" "io"
"os" "os"
"strings" "strings"
"sync"
) )
type FileWriter struct { type FileWriter struct {
@@ -15,6 +17,7 @@ type FileWriter struct {
bodySize int64 bodySize int64
expiredAt int64 expiredAt int64
endFunc func() endFunc func()
once sync.Once
} }
func NewFileWriter(rawWriter *os.File, key string, expiredAt int64, endFunc func()) *FileWriter { func NewFileWriter(rawWriter *os.File, key string, expiredAt int64, endFunc func()) *FileWriter {
@@ -63,6 +66,13 @@ func (this *FileWriter) Write(data []byte) (n int, err error) {
return return
} }
// WriteAt 在指定位置写入数据
func (this *FileWriter) WriteAt(data []byte, offset int64) error {
_ = data
_ = offset
return errors.New("not supported")
}
// WriteBodyLength 写入Body长度数据 // WriteBodyLength 写入Body长度数据
func (this *FileWriter) WriteBodyLength(bodyLength int64) error { func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
bytes8 := make([]byte, 8) bytes8 := make([]byte, 8)
@@ -82,18 +92,25 @@ func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
// Close 关闭 // Close 关闭
func (this *FileWriter) Close() error { func (this *FileWriter) Close() error {
defer this.endFunc() defer this.once.Do(func() {
this.endFunc()
})
path := this.rawWriter.Name()
err := this.WriteHeaderLength(types.Int(this.headerSize)) err := this.WriteHeaderLength(types.Int(this.headerSize))
if err != nil { if err != nil {
_ = this.rawWriter.Close()
_ = os.Remove(path)
return err return err
} }
err = this.WriteBodyLength(this.bodySize) err = this.WriteBodyLength(this.bodySize)
if err != nil { if err != nil {
_ = this.rawWriter.Close()
_ = os.Remove(path)
return err return err
} }
path := this.rawWriter.Name()
err = this.rawWriter.Close() err = this.rawWriter.Close()
if err != nil { if err != nil {
_ = os.Remove(path) _ = os.Remove(path)
@@ -109,7 +126,9 @@ func (this *FileWriter) Close() error {
// Discard 丢弃 // Discard 丢弃
func (this *FileWriter) Discard() error { func (this *FileWriter) Discard() error {
defer this.endFunc() defer this.once.Do(func() {
this.endFunc()
})
_ = this.rawWriter.Close() _ = this.rawWriter.Close()

View File

@@ -1,7 +1,9 @@
package caches package caches
import ( import (
"errors"
"github.com/cespare/xxhash" "github.com/cespare/xxhash"
"sync"
"time" "time"
) )
@@ -18,6 +20,7 @@ type MemoryWriter struct {
hash uint64 hash uint64
item *MemoryItem item *MemoryItem
endFunc func() endFunc func()
once sync.Once
} }
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, endFunc func()) *MemoryWriter { func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, endFunc func()) *MemoryWriter {
@@ -53,6 +56,13 @@ func (this *MemoryWriter) Write(data []byte) (n int, err error) {
return len(data), nil return len(data), nil
} }
// WriteAt 在指定位置写入数据
func (this *MemoryWriter) WriteAt(b []byte, offset int64) error {
_ = b
_ = offset
return errors.New("not supported")
}
// HeaderSize 数据尺寸 // HeaderSize 数据尺寸
func (this *MemoryWriter) HeaderSize() int64 { func (this *MemoryWriter) HeaderSize() int64 {
return this.headerSize return this.headerSize
@@ -66,7 +76,9 @@ func (this *MemoryWriter) BodySize() int64 {
// Close 关闭 // Close 关闭
func (this *MemoryWriter) Close() error { func (this *MemoryWriter) Close() error {
// 需要在Locker之外 // 需要在Locker之外
defer this.endFunc() defer this.once.Do(func() {
this.endFunc()
})
if this.item == nil { if this.item == nil {
return nil return nil
@@ -92,7 +104,9 @@ func (this *MemoryWriter) Close() error {
// Discard 丢弃 // Discard 丢弃
func (this *MemoryWriter) Discard() error { func (this *MemoryWriter) Discard() error {
// 需要在Locker之外 // 需要在Locker之外
defer this.endFunc() defer this.once.Do(func() {
this.endFunc()
})
this.storage.locker.Lock() this.storage.locker.Lock()
delete(this.storage.valuesMap, this.hash) delete(this.storage.valuesMap, this.hash)

View File

@@ -0,0 +1,173 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches
import (
"encoding/binary"
"github.com/iwind/TeaGo/types"
"io"
"os"
"strings"
"sync"
)
type PartialFileWriter struct {
rawWriter *os.File
key string
headerSize int64
bodySize int64
expiredAt int64
endFunc func()
once sync.Once
isNew bool
isPartial bool
bodyOffset int64
}
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew bool, isPartial bool, bodyOffset int64, endFunc func()) *PartialFileWriter {
return &PartialFileWriter{
key: key,
rawWriter: rawWriter,
expiredAt: expiredAt,
endFunc: endFunc,
isNew: isNew,
isPartial: isPartial,
bodyOffset: bodyOffset,
}
}
// WriteHeader 写入数据
func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
if !this.isNew {
return
}
n, err = this.rawWriter.Write(data)
this.headerSize += int64(n)
if err != nil {
_ = this.Discard()
}
return
}
// WriteHeaderLength 写入Header长度数据
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
bytes4 := make([]byte, 4)
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
if err != nil {
_ = this.Discard()
return err
}
_, err = this.rawWriter.Write(bytes4)
if err != nil {
_ = this.Discard()
return err
}
return nil
}
// Write 写入数据
func (this *PartialFileWriter) Write(data []byte) (n int, err error) {
n, err = this.rawWriter.Write(data)
this.bodySize += int64(n)
if err != nil {
_ = this.Discard()
}
return
}
// WriteAt 在指定位置写入数据
func (this *PartialFileWriter) WriteAt(data []byte, offset int64) error {
if this.bodyOffset == 0 {
this.bodyOffset = SizeMeta + int64(len(this.key)) + this.headerSize
}
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
return err
}
// WriteBodyLength 写入Body长度数据
func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
bytes8 := make([]byte, 8)
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
if err != nil {
_ = this.Discard()
return err
}
_, err = this.rawWriter.Write(bytes8)
if err != nil {
_ = this.Discard()
return err
}
return nil
}
// Close 关闭
func (this *PartialFileWriter) Close() error {
defer this.once.Do(func() {
this.endFunc()
})
var path = this.rawWriter.Name()
if this.isNew {
err := this.WriteHeaderLength(types.Int(this.headerSize))
if err != nil {
_ = this.rawWriter.Close()
_ = os.Remove(path)
return err
}
err = this.WriteBodyLength(this.bodySize)
if err != nil {
_ = this.rawWriter.Close()
_ = os.Remove(path)
return err
}
}
err := this.rawWriter.Close()
if err != nil {
_ = os.Remove(path)
} else if !this.isPartial {
err = os.Rename(path, strings.Replace(path, ".tmp", "", 1))
if err != nil {
_ = os.Remove(path)
}
}
return err
}
// Discard 丢弃
func (this *PartialFileWriter) Discard() error {
defer this.once.Do(func() {
this.endFunc()
})
_ = this.rawWriter.Close()
err := os.Remove(this.rawWriter.Name())
return err
}
func (this *PartialFileWriter) HeaderSize() int64 {
return this.headerSize
}
func (this *PartialFileWriter) BodySize() int64 {
return this.bodySize
}
func (this *PartialFileWriter) ExpiredAt() int64 {
return this.expiredAt
}
func (this *PartialFileWriter) Key() string {
return this.key
}
// ItemType 获取内容类型
func (this *PartialFileWriter) ItemType() ItemType {
return ItemTypeFile
}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"testing"
"time"
)
func TestPartialFileWriter_SeekOffset(t *testing.T) {
var path = "/tmp/test@partial.cache"
_ = os.Remove(path)
var reader = func() {
data, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
t.Log("["+types.String(len(data))+"]", string(data))
}
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
t.Fatal(err)
}
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, true, true, 0, func() {
t.Log("end")
})
_, err = writer.WriteHeader([]byte("header"))
if err != nil {
t.Fatal(err)
}
// 移动位置
err = writer.WriteAt([]byte("HELLO"), 100)
if err != nil {
t.Fatal(err)
}
err = writer.Close()
if err != nil {
t.Fatal(err)
}
reader()
}

View File

@@ -5,6 +5,7 @@ package compressions
import ( import (
"github.com/andybalholm/brotli" "github.com/andybalholm/brotli"
"io" "io"
"strings"
) )
type BrotliReader struct { type BrotliReader struct {
@@ -16,7 +17,11 @@ func NewBrotliReader(reader io.Reader) (Reader, error) {
} }
func (this *BrotliReader) Read(p []byte) (n int, err error) { func (this *BrotliReader) Read(p []byte) (n int, err error) {
return this.reader.Read(p) n, err = this.reader.Read(p)
if err != nil && strings.Contains(err.Error(), "excessive") {
err = io.EOF
}
return
} }
func (this *BrotliReader) Close() error { func (this *BrotliReader) Close() error {

View File

@@ -0,0 +1,68 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package compressions
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"io"
"os"
"testing"
)
func TestGzipReader(t *testing.T) {
fp, err := os.Open("/Users/WorkSpace/EdgeProject/EdgeCache/p43/36/7e/367e02720713fe05b66573a1d69b4f0a.cache")
if err != nil {
// not fatal
t.Log(err)
return
}
defer func() {
_ = fp.Close()
}()
var buf = make([]byte, 32*1024)
cacheReader := caches.NewFileReader(fp)
err = cacheReader.Init()
if err != nil {
t.Fatal(err)
}
var headerBuf = []byte{}
err = cacheReader.ReadHeader(buf, func(n int) (goNext bool, err error) {
headerBuf = append(headerBuf, buf[:n]...)
for {
nIndex := bytes.Index(headerBuf, []byte{'\n'})
if nIndex >= 0 {
row := headerBuf[:nIndex]
spaceIndex := bytes.Index(row, []byte{':'})
if spaceIndex <= 0 {
return false, errors.New("invalid header '" + string(row) + "'")
}
headerBuf = headerBuf[nIndex+1:]
} else {
break
}
}
return true, nil
})
reader, err := NewGzipReader(cacheReader)
if err != nil {
t.Fatal(err)
}
for {
n, err := reader.Read(buf)
if err != nil {
if err != io.EOF {
t.Fatal(err)
} else {
break
}
}
t.Log(string(buf[:n]))
_ = n
}
}

View File

@@ -1,4 +1,5 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build community
// +build community // +build community
package teaconst package teaconst

View File

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

View File

@@ -1,7 +1,7 @@
package teaconst package teaconst
const ( const (
Version = "0.3.5" Version = "0.4.2"
ProductName = "Edge Node" ProductName = "Edge Node"
ProcessName = "edge-node" ProcessName = "edge-node"

View File

@@ -2,11 +2,16 @@
package teaconst package teaconst
import "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
var ( var (
// 流量统计 // 流量统计
InTrafficBytes = uint64(0) InTrafficBytes = uint64(0)
OutTrafficBytes = uint64(0) OutTrafficBytes = uint64(0)
NodeId int64 = 0 NodeId int64 = 0
NodeIdString = ""
GlobalProductName = nodeconfigs.DefaultProductName
) )

View File

@@ -1,27 +1,71 @@
package events package events
import "sync" import (
"sync"
)
var eventsMap = map[string][]func(){} // event => []callbacks type Callbacks = []func()
var eventsMap = map[Event]map[interface{}]Callbacks{} // event => map[event key][]callback
var locker = sync.Mutex{} var locker = sync.Mutex{}
// 增加事件回调 var eventKeyId = 0
func On(event string, callback func()) {
func NewKey() interface{} {
locker.Lock()
defer locker.Unlock()
eventKeyId++
return eventKeyId
}
// On 增加事件回调
func On(event Event, callback func()) {
OnKey(event, nil, callback)
}
// OnKey 使用Key增加事件回调
func OnKey(event Event, key interface{}, callback func()) {
if key == nil {
key = NewKey()
}
locker.Lock() locker.Lock()
defer locker.Unlock() defer locker.Unlock()
callbacks, _ := eventsMap[event] m, ok := eventsMap[event]
callbacks = append(callbacks, callback) if !ok {
eventsMap[event] = callbacks m = map[interface{}]Callbacks{}
eventsMap[event] = m
}
m[key] = append(m[key], callback)
} }
// 通知事件 // Remove 删除事件回调
func Notify(event string) { func Remove(key interface{}) {
if key == nil {
return
}
locker.Lock() locker.Lock()
callbacks, _ := eventsMap[event] for k, m := range eventsMap {
_, ok := m[key]
if ok {
delete(m, key)
eventsMap[k] = m
}
}
locker.Unlock()
}
// Notify 通知事件
func Notify(event Event) {
locker.Lock()
m := eventsMap[event]
locker.Unlock() locker.Unlock()
for _, callback := range callbacks { for _, callbacks := range m {
callback() for _, callback := range callbacks {
callback()
}
} }
} }

View File

@@ -1,16 +1,33 @@
package events package events_test
import "testing" import (
"github.com/TeaOSLab/EdgeNode/internal/events"
"testing"
)
func TestOn(t *testing.T) { func TestOn(t *testing.T) {
On("hello", func() { type User struct {
name string
}
var u = &User{}
var u2 = &User{}
events.On("hello", func() {
t.Log("world") t.Log("world")
}) })
On("hello", func() { events.On("hello", func() {
t.Log("world2") t.Log("world2")
}) })
On("hello2", func() { events.OnKey("hello", u, func() {
t.Log("world3")
})
events.OnKey("hello", u, func() {
t.Log("world4")
})
events.Remove(u)
events.Remove(u2)
events.OnKey("hello2", nil, func() {
t.Log("world2") t.Log("world2")
}) })
Notify("hello") events.Notify("hello")
} }

View File

@@ -0,0 +1,42 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package firewalls
import (
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
)
var currentFirewall FirewallInterface
// 初始化
func init() {
events.On(events.EventLoaded, func() {
var firewall = Firewall()
if firewall.Name() == "mock" {
remotelogs.Warn("FIREWALL", "'firewalld' on this system should be enabled to block attackers more effectively")
} else {
remotelogs.Println("FIREWALL", "found local firewall '"+firewall.Name()+"'")
}
})
}
// Firewall 查找当前系统中最适合的防火墙
func Firewall() FirewallInterface {
if currentFirewall != nil {
return currentFirewall
}
// firewalld
{
var firewalld = NewFirewalld()
if firewalld.IsReady() {
currentFirewall = firewalld
return currentFirewall
}
}
// 至少返回一个
currentFirewall = NewMockFirewall()
return currentFirewall
}

View File

@@ -0,0 +1,135 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package firewalls
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/types"
"os/exec"
"strings"
)
type Firewalld struct {
isReady bool
exe string
cmdQueue chan *exec.Cmd
}
func NewFirewalld() *Firewalld {
var firewalld = &Firewalld{
cmdQueue: make(chan *exec.Cmd, 4096),
}
path, err := exec.LookPath("firewall-cmd")
if err == nil && len(path) > 0 {
var cmd = exec.Command(path, "-V")
err := cmd.Run()
if err == nil {
firewalld.exe = path
firewalld.isReady = true
firewalld.init()
}
}
return firewalld
}
func (this *Firewalld) init() {
goman.New(func() {
for cmd := range this.cmdQueue {
err := cmd.Run()
if err != nil {
if strings.HasPrefix(err.Error(), "Warning:") {
continue
}
remotelogs.Warn("FIREWALL", "run command failed '"+cmd.String()+"': "+err.Error())
}
}
})
}
// Name 名称
func (this *Firewalld) Name() string {
return "firewalld"
}
func (this *Firewalld) IsReady() bool {
return this.isReady
}
func (this *Firewalld) AllowPort(port int, protocol string) error {
if !this.isReady {
return nil
}
var cmd = exec.Command(this.exe, "--add-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd)
return nil
}
func (this *Firewalld) RemovePort(port int, protocol string) error {
if !this.isReady {
return nil
}
var cmd = exec.Command(this.exe, "--remove-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd)
return nil
}
func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
if !this.isReady {
return nil
}
var family = "ipv4"
if strings.Contains(ip, ":") {
family = "ipv6"
}
var args = []string{"--add-rich-rule=rule family='" + family + "' source address='" + ip + "' reject"}
if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
}
var cmd = exec.Command(this.exe, args...)
this.pushCmd(cmd)
return nil
}
func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int) error {
if !this.isReady {
return nil
}
var family = "ipv4"
if strings.Contains(ip, ":") {
family = "ipv6"
}
var args = []string{"--add-rich-rule=rule family='" + family + "' source address='" + ip + "' drop"}
if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
}
var cmd = exec.Command(this.exe, args...)
this.pushCmd(cmd)
return nil
}
func (this *Firewalld) RemoveSourceIP(ip string) error {
if !this.isReady {
return nil
}
var family = "ipv4"
if strings.Contains(ip, ":") {
family = "ipv6"
}
for _, action := range []string{"reject", "drop"} {
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
var cmd = exec.Command(this.exe, args...)
this.pushCmd(cmd)
}
return nil
}
func (this *Firewalld) pushCmd(cmd *exec.Cmd) {
select {
case this.cmdQueue <- cmd:
default:
// we discard the command
}
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package firewalls
// FirewallInterface 防火墙接口
type FirewallInterface interface {
// Name 名称
Name() string
// IsReady 是否已准备被调用
IsReady() bool
// AllowPort 允许端口
AllowPort(port int, protocol string) error
// RemovePort 删除端口
RemovePort(port int, protocol string) error
// RejectSourceIP 拒绝某个源IP连接
RejectSourceIP(ip string, timeoutSeconds int) error
// DropSourceIP 丢弃某个源IP数据
DropSourceIP(ip string, timeoutSeconds int) error
// RemoveSourceIP 删除某个源IP
RemoveSourceIP(ip string) error
}

View File

@@ -0,0 +1,55 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package firewalls
// MockFirewall 模拟防火墙
type MockFirewall struct {
}
func NewMockFirewall() *MockFirewall {
return &MockFirewall{}
}
// Name 名称
func (this *MockFirewall) Name() string {
return "mock"
}
// IsReady 是否已准备被调用
func (this *MockFirewall) IsReady() bool {
return true
}
// AllowPort 允许端口
func (this *MockFirewall) AllowPort(port int, protocol string) error {
_ = port
_ = protocol
return nil
}
// RemovePort 删除端口
func (this *MockFirewall) RemovePort(port int, protocol string) error {
_ = port
_ = protocol
return nil
}
// RejectSourceIP 拒绝某个源IP连接
func (this *MockFirewall) RejectSourceIP(ip string, timeoutSeconds int) error {
_ = ip
_ = timeoutSeconds
return nil
}
// DropSourceIP 丢弃某个源IP数据
func (this *MockFirewall) DropSourceIP(ip string, timeoutSeconds int) error {
_ = ip
_ = timeoutSeconds
return nil
}
// RemoveSourceIP 删除某个源IP
func (this *MockFirewall) RemoveSourceIP(ip string) error {
_ = ip
return nil
}

View File

@@ -0,0 +1,12 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
import "time"
type Instance struct {
Id uint64
CreatedTime time.Time
File string
Line int
}

81
internal/goman/lib.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
import (
"runtime"
"sync"
"time"
)
var locker = &sync.Mutex{}
var instanceMap = map[uint64]*Instance{} // id => *Instance
var instanceId = uint64(0)
// New 新创建goroutine
func New(f func()) {
_, file, line, _ := runtime.Caller(1)
go func() {
locker.Lock()
instanceId++
var instance = &Instance{
Id: instanceId,
CreatedTime: time.Now(),
}
instance.File = file
instance.Line = line
instanceMap[instanceId] = instance
locker.Unlock()
// run function
f()
locker.Lock()
delete(instanceMap, instanceId)
locker.Unlock()
}()
}
// NewWithArgs 创建带有参数的goroutine
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
_, file, line, _ := runtime.Caller(1)
go func() {
locker.Lock()
instanceId++
var instance = &Instance{
Id: instanceId,
CreatedTime: time.Now(),
}
instance.File = file
instance.Line = line
instanceMap[instanceId] = instance
locker.Unlock()
// run function
f(args...)
locker.Lock()
delete(instanceMap, instanceId)
locker.Unlock()
}()
}
// List 列出所有正在运行goroutine
func List() []*Instance {
locker.Lock()
defer locker.Unlock()
var result = []*Instance{}
for _, instance := range instanceMap {
result = append(result, instance)
}
return result
}

View File

@@ -0,0 +1,28 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
import (
"testing"
"time"
)
func TestNew(t *testing.T) {
New(func() {
t.Log("Hello")
t.Log(List())
})
time.Sleep(1 * time.Second)
t.Log(List())
time.Sleep(1 * time.Second)
}
func TestNewWithArgs(t *testing.T) {
NewWithArgs(func(args ...interface{}) {
t.Log(args[0], args[1])
}, 1, 2)
time.Sleep(1 * time.Second)
}

View File

@@ -71,7 +71,7 @@ func (this *HTTPAPIAction) runAction(action string, listType IPListType, item *p
if err != nil { if err != nil {
return err return err
} }
req.Header.Set("User-Agent", "GoEdge-Node/"+teaconst.Version) req.Header.Set("User-Agent", teaconst.GlobalProductName+"-Node/"+teaconst.Version)
resp, err := httpAPIClient.Do(req) resp, err := httpAPIClient.Do(req)
if err != nil { if err != nil {
return err return err

View File

@@ -297,10 +297,13 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
case "deleteItem": case "deleteItem":
args = append(args, "del") args = append(args, "del")
} }
args = append(args, listName, item.IpFrom) args = append(args, listName, item.IpFrom)
timestamp := time.Now().Unix() if action == "addItem" {
if item.ExpiredAt > timestamp { timestamp := time.Now().Unix()
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10)) if item.ExpiredAt > timestamp {
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10))
}
} }
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {

View File

@@ -0,0 +1,130 @@
// 源码改自https://github.com/lionsoul2014/ip2region/blob/master/binding/golang/ip2region/ip2Region.go
package iplibrary
import (
"errors"
"io/ioutil"
"strconv"
"strings"
)
const (
IndexBlockLength = 12
)
var err error
type IP2Region struct {
headerSip []int64
headerPtr []int64
headerLen int64
// super block index info
firstIndexPtr int64
lastIndexPtr int64
totalBlocks int64
dbData []byte
}
type IpInfo struct {
CityId int64
Country string
Region string
Province string
City string
ISP string
}
func (ip IpInfo) String() string {
return strconv.FormatInt(ip.CityId, 10) + "|" + ip.Country + "|" + ip.Region + "|" + ip.Province + "|" + ip.City + "|" + ip.ISP
}
func getIpInfo(cityId int64, line []byte) *IpInfo {
lineSlice := strings.Split(string(line), "|")
ipInfo := &IpInfo{}
length := len(lineSlice)
ipInfo.CityId = cityId
if length < 5 {
for i := 0; i <= 5-length; i++ {
lineSlice = append(lineSlice, "")
}
}
ipInfo.Country = lineSlice[0]
ipInfo.Region = lineSlice[1]
ipInfo.Province = lineSlice[2]
ipInfo.City = lineSlice[3]
ipInfo.ISP = lineSlice[4]
return ipInfo
}
func NewIP2Region(path string) (*IP2Region, error) {
var region = &IP2Region{}
region.dbData, err = ioutil.ReadFile(path)
if err != nil {
return nil, err
}
region.firstIndexPtr = region.ipLongAtOffset(0)
region.lastIndexPtr = region.ipLongAtOffset(4)
region.totalBlocks = (region.lastIndexPtr-region.firstIndexPtr)/IndexBlockLength + 1
return region, nil
}
func (this *IP2Region) MemorySearch(ipStr string) (ipInfo *IpInfo, err error) {
ip, err := ip2long(ipStr)
if err != nil {
return nil, err
}
h := this.totalBlocks
var dataPtr, l int64
for l <= h {
m := (l + h) >> 1
p := this.firstIndexPtr + m*IndexBlockLength
sip := this.ipLongAtOffset(p)
if ip < sip {
h = m - 1
} else {
eip := this.ipLongAtOffset(p + 4)
if ip > eip {
l = m + 1
} else {
dataPtr = this.ipLongAtOffset(p + 8)
break
}
}
}
if dataPtr == 0 {
return nil, errors.New("not found")
}
dataLen := (dataPtr >> 24) & 0xFF
dataPtr = dataPtr & 0x00FFFFFF
return getIpInfo(this.ipLongAtOffset(dataPtr), this.dbData[(dataPtr)+4:dataPtr+dataLen]), nil
}
func (this *IP2Region) ipLongAtOffset(offset int64) int64 {
return int64(this.dbData[offset]) |
int64(this.dbData[offset+1])<<8 |
int64(this.dbData[offset+2])<<16 |
int64(this.dbData[offset+3])<<24
}
func ip2long(IpStr string) (int64, error) {
bits := strings.Split(IpStr, ".")
if len(bits) != 4 {
return 0, errors.New("ip format error")
}
var sum int64
for i, n := range bits {
bit, _ := strconv.ParseInt(n, 10, 64)
sum += bit << uint(24-8*i)
}
return sum, nil
}

View File

@@ -29,11 +29,9 @@ func NewIPList() *IPList {
} }
expireList := expires.NewList() expireList := expires.NewList()
go func() { expireList.OnGC(func(itemId int64) {
expireList.StartGC(func(itemId int64) { list.Delete(itemId)
list.Delete(itemId) })
})
}()
list.expireList = expireList list.expireList = expireList
return list return list
} }

View File

@@ -6,7 +6,9 @@ import (
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"runtime" "runtime"
"runtime/debug"
"strconv" "strconv"
"sync"
"testing" "testing"
"time" "time"
) )
@@ -281,6 +283,22 @@ func TestGC(t *testing.T) {
logs.PrintAsJSON(list.sortedItems, t) logs.PrintAsJSON(list.sortedItems, t)
} }
func TestTooManyLists(t *testing.T) {
debug.SetMaxThreads(20)
var lists = []*IPList{}
var locker = &sync.Mutex{}
for i := 0; i < 1000; i++ {
locker.Lock()
lists = append(lists, NewIPList())
locker.Unlock()
}
time.Sleep(1 * time.Second)
t.Log(runtime.NumGoroutine())
t.Log(len(lists), "lists")
}
func BenchmarkIPList_Contains(b *testing.B) { func BenchmarkIPList_Contains(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)

View File

@@ -4,17 +4,16 @@ import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeNode/internal/errors" "github.com/TeaOSLab/EdgeNode/internal/errors"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/lionsoul2014/ip2region/binding/golang/ip2region"
"net" "net"
"strings" "strings"
) )
type IP2RegionLibrary struct { type IP2RegionLibrary struct {
db *ip2region.Ip2Region db *IP2Region
} }
func (this *IP2RegionLibrary) Load(dbPath string) error { func (this *IP2RegionLibrary) Load(dbPath string) error {
db, err := ip2region.New(dbPath) db, err := NewIP2Region(dbPath)
if err != nil { if err != nil {
return err return err
} }
@@ -49,6 +48,10 @@ func (this *IP2RegionLibrary) Lookup(ip string) (*Result, error) {
return nil, err return nil, err
} }
if info == nil {
return nil, nil
}
if info.Country == "0" { if info.Country == "0" {
info.Country = "" info.Country = ""
} }
@@ -76,7 +79,5 @@ func (this *IP2RegionLibrary) Lookup(ip string) (*Result, error) {
} }
func (this *IP2RegionLibrary) Close() { func (this *IP2RegionLibrary) Close() {
if this.db != nil {
this.db.Close()
}
} }

View File

@@ -3,25 +3,83 @@ package iplibrary
import ( import (
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"runtime" "runtime"
"strconv" "strconv"
"sync"
"testing" "testing"
"time" "time"
) )
func TestIP2RegionLibrary_Lookup_MemoryUsage(t *testing.T) {
var mem = &runtime.MemStats{}
runtime.ReadMemStats(mem)
library := &IP2RegionLibrary{}
err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db")
if err != nil {
t.Fatal(err)
}
var mem2 = &runtime.MemStats{}
runtime.ReadMemStats(mem2)
t.Log((mem2.HeapInuse-mem.HeapInuse)/1024/1024, "MB")
}
func TestIP2RegionLibrary_Lookup_Single(t *testing.T) {
library := &IP2RegionLibrary{}
err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db")
if err != nil {
t.Fatal(err)
}
for _, ip := range []string{"8.8.9.9"} {
result, err := library.Lookup(ip)
if err != nil {
t.Fatal(err)
}
t.Log("IP:", ip, "result:", result)
}
}
func TestIP2RegionLibrary_Lookup(t *testing.T) { func TestIP2RegionLibrary_Lookup(t *testing.T) {
library := &IP2RegionLibrary{} library := &IP2RegionLibrary{}
err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db") err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
result, err := library.Lookup("114.240.223.47")
for _, ip := range []string{"", "a", "1.1.1", "192.168.1.100", "114.240.223.47", "8.8.9.9", "::1"} {
result, err := library.Lookup(ip)
if err != nil {
t.Fatal(err)
}
t.Log("IP:", ip, "result:", result)
}
}
func TestIP2RegionLibrary_Lookup_Concurrent(t *testing.T) {
library := &IP2RegionLibrary{}
err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
logs.PrintAsJSON(result, t)
var count = 4000
var wg = sync.WaitGroup{}
wg.Add(count)
for i := 0; i < count; i++ {
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
_, _ = library.Lookup(strconv.Itoa(rands.Int(0, 254)) + "." + strconv.Itoa(rands.Int(0, 254)) + "." + strconv.Itoa(rands.Int(0, 254)) + "." + strconv.Itoa(rands.Int(0, 254)))
}
}()
}
wg.Done()
t.Log("ok")
} }
func TestIP2RegionLibrary_Memory(t *testing.T) { func TestIP2RegionLibrary_Memory(t *testing.T) {
@@ -43,13 +101,13 @@ func TestIP2RegionLibrary_Memory(t *testing.T) {
func BenchmarkIP2RegionLibrary_Lookup(b *testing.B) { func BenchmarkIP2RegionLibrary_Lookup(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
library := &IP2RegionLibrary{} var library = &IP2RegionLibrary{}
err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db") err := library.Load(Tea.Root + "/resources/ipdata/ip2region/ip2region.db")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = library.Lookup(strconv.Itoa(rands.Int(0, 254)) + "." + strconv.Itoa(rands.Int(0, 254)) + "." + strconv.Itoa(rands.Int(0, 254)) + "." + strconv.Itoa(rands.Int(0, 254))) _, _ = library.Lookup("8.8.8.8")
} }
} }

View File

@@ -7,6 +7,7 @@ import (
) )
// AllowIP 检查IP是否被允许访问 // AllowIP 检查IP是否被允许访问
// 如果一个IP不在任何名单中则允许访问
func AllowIP(ip string, serverId int64) bool { func AllowIP(ip string, serverId int64) bool {
var ipLong = utils.IP2Long(ip) var ipLong = utils.IP2Long(ip)
if ipLong == 0 { if ipLong == 0 {
@@ -40,6 +41,17 @@ func AllowIP(ip string, serverId int64) bool {
return true return true
} }
// IsInWhiteList 检查IP是否在白名单中
func IsInWhiteList(ip string) bool {
var ipLong = utils.IP2Long(ip)
if ipLong == 0 {
return false
}
// check white lists
return GlobalWhiteIPList.Contains(ipLong)
}
// AllowIPStrings 检查一组IP是否被允许访问 // AllowIPStrings 检查一组IP是否被允许访问
func AllowIPStrings(ipStrings []string, serverId int64) bool { func AllowIPStrings(ipStrings []string, serverId int64) bool {
if len(ipStrings) == 0 { if len(ipStrings) == 0 {

View File

@@ -0,0 +1,156 @@
package iplibrary
import (
"crypto/md5"
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"sync"
"time"
)
var SharedCityManager = NewCityManager()
func init() {
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedCityManager.Start()
})
})
events.On(events.EventQuit, func() {
SharedCityManager.Stop()
})
}
// CityManager 中国省份信息管理
type CityManager struct {
ticker *time.Ticker
cacheFile string
cityMap map[string]int64 // provinceName_cityName => cityName
dataHash string // 国家JSON的md5
locker sync.RWMutex
isUpdated bool
}
func NewCityManager() *CityManager {
return &CityManager{
cacheFile: Tea.Root + "/configs/region_city.json.cache",
cityMap: map[string]int64{},
}
}
func (this *CityManager) Start() {
// 从缓存中读取
err := this.load()
if err != nil {
remotelogs.ErrorObject("CITY_MANAGER", err)
}
// 第一次更新
err = this.loop()
if err != nil {
remotelogs.ErrorObject("City_MANAGER", err)
}
// 定时更新
this.ticker = time.NewTicker(4 * time.Hour)
for range this.ticker.C {
err := this.loop()
if err != nil {
remotelogs.ErrorObject("CITY_MANAGER", err)
}
}
}
func (this *CityManager) Stop() {
if this.ticker != nil {
this.ticker.Stop()
}
}
func (this *CityManager) Lookup(provinceId int64, cityName string) (cityId int64) {
this.locker.RLock()
cityId, _ = this.cityMap[types.String(provinceId)+"_"+cityName]
this.locker.RUnlock()
return
}
// 从缓存中读取
func (this *CityManager) load() error {
data, err := ioutil.ReadFile(this.cacheFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
m := map[string]int64{}
err = json.Unmarshal(data, &m)
if err != nil {
return err
}
if m != nil && len(m) > 0 {
this.cityMap = m
}
return nil
}
// 更新城市信息
func (this *CityManager) loop() error {
if this.isUpdated {
return nil
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
resp, err := rpcClient.RegionCityRPC().FindAllEnabledRegionCities(rpcClient.Context(), &pb.FindAllEnabledRegionCitiesRequest{})
if err != nil {
return err
}
m := map[string]int64{}
for _, city := range resp.RegionCities {
for _, code := range city.Codes {
m[types.String(city.RegionProvinceId)+"_"+code] = city.Id
}
}
// 检查是否有更新
data, err := json.Marshal(m)
if err != nil {
return err
}
hash := md5.New()
hash.Write(data)
dataHash := fmt.Sprintf("%x", hash.Sum(nil))
if this.dataHash == dataHash {
return nil
}
this.dataHash = dataHash
this.locker.Lock()
this.cityMap = m
this.isUpdated = true
this.locker.Unlock()
// 保存到本地缓存
err = ioutil.WriteFile(this.cacheFile, data, 0666)
return err
}

View File

@@ -0,0 +1,14 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplibrary
import "testing"
func TestNewCityManager(t *testing.T) {
var manager = NewCityManager()
err := manager.loop()
if err != nil {
t.Fatal(err)
}
t.Log(manager.Lookup(16, "许昌市"))
}

View File

@@ -6,9 +6,9 @@ import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"io/ioutil" "io/ioutil"
@@ -21,18 +21,27 @@ var SharedCountryManager = NewCountryManager()
func init() { func init() {
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
go SharedCountryManager.Start() goman.New(func() {
SharedCountryManager.Start()
})
})
events.On(events.EventQuit, func() {
SharedCountryManager.Stop()
}) })
} }
// CountryManager 国家/地区信息管理 // CountryManager 国家/地区信息管理
type CountryManager struct { type CountryManager struct {
ticker *time.Ticker
cacheFile string cacheFile string
countryMap map[string]int64 // countryName => countryId countryMap map[string]int64 // countryName => countryId
dataHash string // 国家JSON的md5 dataHash string // 国家JSON的md5
locker sync.RWMutex locker sync.RWMutex
isUpdated bool
} }
func NewCountryManager() *CountryManager { func NewCountryManager() *CountryManager {
@@ -56,11 +65,8 @@ func (this *CountryManager) Start() {
} }
// 定时更新 // 定时更新
ticker := utils.NewTicker(1 * time.Hour) this.ticker = time.NewTicker(4 * time.Hour)
events.On(events.EventQuit, func() { for range this.ticker.C {
ticker.Stop()
})
for range ticker.C {
err := this.loop() err := this.loop()
if err != nil { if err != nil {
remotelogs.ErrorObject("COUNTRY_MANAGER", err) remotelogs.ErrorObject("COUNTRY_MANAGER", err)
@@ -68,6 +74,12 @@ func (this *CountryManager) Start() {
} }
} }
func (this *CountryManager) Stop() {
if this.ticker != nil {
this.ticker.Stop()
}
}
func (this *CountryManager) Lookup(countryName string) (countryId int64) { func (this *CountryManager) Lookup(countryName string) (countryId int64) {
this.locker.RLock() this.locker.RLock()
countryId, _ = this.countryMap[countryName] countryId, _ = this.countryMap[countryName]
@@ -98,6 +110,10 @@ func (this *CountryManager) load() error {
// 更新国家信息 // 更新国家信息
func (this *CountryManager) loop() error { func (this *CountryManager) loop() error {
if this.isUpdated {
return nil
}
rpcClient, err := rpc.SharedRPC() rpcClient, err := rpc.SharedRPC()
if err != nil { if err != nil {
return err return err
@@ -108,7 +124,7 @@ func (this *CountryManager) loop() error {
} }
m := map[string]int64{} m := map[string]int64{}
for _, country := range resp.Countries { for _, country := range resp.RegionCountries {
for _, code := range country.Codes { for _, code := range country.Codes {
m[code] = country.Id m[code] = country.Id
} }
@@ -129,6 +145,7 @@ func (this *CountryManager) loop() error {
this.locker.Lock() this.locker.Lock()
this.countryMap = m this.countryMap = m
this.isUpdated = true
this.locker.Unlock() this.locker.Unlock()
// 保存到本地缓存 // 保存到本地缓存

View File

@@ -3,10 +3,12 @@ package iplibrary
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"sync" "sync"
"time" "time"
@@ -17,12 +19,19 @@ var IPListUpdateNotify = make(chan bool, 1)
func init() { func init() {
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
go SharedIPListManager.Start() goman.New(func() {
SharedIPListManager.Start()
})
})
events.On(events.EventQuit, func() {
SharedIPListManager.Stop()
}) })
} }
// IPListManager IP名单管理 // IPListManager IP名单管理
type IPListManager struct { type IPListManager struct {
ticker *time.Ticker
db *IPListDB db *IPListDB
version int64 version int64
@@ -48,17 +57,14 @@ func (this *IPListManager) Start() {
remotelogs.ErrorObject("IP_LIST_MANAGER", err) remotelogs.ErrorObject("IP_LIST_MANAGER", err)
} }
ticker := time.NewTicker(60 * time.Second) this.ticker = time.NewTicker(60 * time.Second)
if Tea.IsTesting() { if Tea.IsTesting() {
ticker = time.NewTicker(10 * time.Second) this.ticker = time.NewTicker(10 * time.Second)
} }
events.On(events.EventQuit, func() {
ticker.Stop()
})
countErrors := 0 countErrors := 0
for { for {
select { select {
case <-ticker.C: case <-this.ticker.C:
case <-IPListUpdateNotify: case <-IPListUpdateNotify:
} }
err := this.loop() err := this.loop()
@@ -80,6 +86,12 @@ func (this *IPListManager) Start() {
} }
} }
func (this *IPListManager) Stop() {
if this.ticker != nil {
this.ticker.Stop()
}
}
func (this *IPListManager) init() { func (this *IPListManager) init() {
// 从数据库中当中读取数据 // 从数据库中当中读取数据
db, err := NewIPListDB() db, err := NewIPListDB()
@@ -161,7 +173,7 @@ func (this *IPListManager) FindList(listId int64) *IPList {
func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool) { func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool) {
this.locker.Lock() this.locker.Lock()
var changedLists = map[*IPList]bool{} var changedLists = map[*IPList]zero.Zero{}
for _, item := range items { for _, item := range items {
var list *IPList var list *IPList
// TODO 实现节点专有List // TODO 实现节点专有List
@@ -187,13 +199,13 @@ func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool)
this.listMap[item.ListId] = list this.listMap[item.ListId] = list
} }
changedLists[list] = true changedLists[list] = zero.New()
if item.IsDeleted { if item.IsDeleted {
list.Delete(item.Id) list.Delete(item.Id)
// 从WAF名单中删除 // 从WAF名单中删除
waf.SharedIPBlackList.RemoveIP(item.IpFrom, item.ServerId) waf.SharedIPBlackList.RemoveIP(item.IpFrom, item.ServerId, shouldExecute)
// 操作事件 // 操作事件
if shouldExecute { if shouldExecute {

View File

@@ -0,0 +1,155 @@
package iplibrary
import (
"crypto/md5"
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"io/ioutil"
"os"
"sync"
"time"
)
var SharedProviderManager = NewProviderManager()
func init() {
events.On(events.EventLoaded, func() {
goman.New(func() {
SharedProviderManager.Start()
})
})
events.On(events.EventQuit, func() {
SharedProviderManager.Stop()
})
}
// ProviderManager 中国省份信息管理
type ProviderManager struct {
ticker *time.Ticker
cacheFile string
providerMap map[string]int64 // name => id
dataHash string // 国家JSON的md5
locker sync.RWMutex
isUpdated bool
}
func NewProviderManager() *ProviderManager {
return &ProviderManager{
cacheFile: Tea.Root + "/configs/region_provider.json.cache",
providerMap: map[string]int64{},
}
}
func (this *ProviderManager) Start() {
// 从缓存中读取
err := this.load()
if err != nil {
remotelogs.ErrorObject("PROVIDER_MANAGER", err)
}
// 第一次更新
err = this.loop()
if err != nil {
remotelogs.ErrorObject("PROVIDER_MANAGER", err)
}
// 定时更新
this.ticker = time.NewTicker(4 * time.Hour)
for range this.ticker.C {
err := this.loop()
if err != nil {
remotelogs.ErrorObject("PROVIDER_MANAGER", err)
}
}
}
func (this *ProviderManager) Stop() {
if this.ticker != nil {
this.ticker.Stop()
}
}
func (this *ProviderManager) Lookup(providerName string) (providerId int64) {
this.locker.RLock()
providerId, _ = this.providerMap[providerName]
this.locker.RUnlock()
return
}
// 从缓存中读取
func (this *ProviderManager) load() error {
data, err := ioutil.ReadFile(this.cacheFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
m := map[string]int64{}
err = json.Unmarshal(data, &m)
if err != nil {
return err
}
if m != nil && len(m) > 0 {
this.providerMap = m
}
return nil
}
// 更新服务商信息
func (this *ProviderManager) loop() error {
if this.isUpdated {
return nil
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
resp, err := rpcClient.RegionProviderRPC().FindAllEnabledRegionProviders(rpcClient.Context(), &pb.FindAllEnabledRegionProvidersRequest{})
if err != nil {
return err
}
m := map[string]int64{}
for _, provider := range resp.RegionProviders {
for _, code := range provider.Codes {
m[code] = provider.Id
}
}
// 检查是否有更新
data, err := json.Marshal(m)
if err != nil {
return err
}
hash := md5.New()
hash.Write(data)
dataHash := fmt.Sprintf("%x", hash.Sum(nil))
if this.dataHash == dataHash {
return nil
}
this.dataHash = dataHash
this.locker.Lock()
this.providerMap = m
this.isUpdated = true
this.locker.Unlock()
// 保存到本地缓存
err = ioutil.WriteFile(this.cacheFile, data, 0666)
return err
}

View File

@@ -0,0 +1,15 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplibrary
import "testing"
func TestNewProviderManager(t *testing.T) {
var manager = NewProviderManager()
err := manager.loop()
if err != nil {
t.Fatal(err)
}
t.Log(manager.Lookup("阿里云"))
t.Log(manager.Lookup("阿里云2"))
}

View File

@@ -6,9 +6,9 @@ import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"io/ioutil" "io/ioutil"
@@ -25,18 +25,27 @@ var SharedProvinceManager = NewProvinceManager()
func init() { func init() {
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
go SharedProvinceManager.Start() goman.New(func() {
SharedProvinceManager.Start()
})
})
events.On(events.EventQuit, func() {
SharedProvinceManager.Stop()
}) })
} }
// ProvinceManager 中国省份信息管理 // ProvinceManager 中国省份信息管理
type ProvinceManager struct { type ProvinceManager struct {
ticker *time.Ticker
cacheFile string cacheFile string
provinceMap map[string]int64 // provinceName => provinceId provinceMap map[string]int64 // provinceName => provinceId
dataHash string // 国家JSON的md5 dataHash string // 国家JSON的md5
locker sync.RWMutex locker sync.RWMutex
isUpdated bool
} }
func NewProvinceManager() *ProvinceManager { func NewProvinceManager() *ProvinceManager {
@@ -60,11 +69,8 @@ func (this *ProvinceManager) Start() {
} }
// 定时更新 // 定时更新
ticker := utils.NewTicker(1 * time.Hour) this.ticker = time.NewTicker(4 * time.Hour)
events.On(events.EventQuit, func() { for range this.ticker.C {
ticker.Stop()
})
for range ticker.C {
err := this.loop() err := this.loop()
if err != nil { if err != nil {
remotelogs.ErrorObject("PROVINCE_MANAGER", err) remotelogs.ErrorObject("PROVINCE_MANAGER", err)
@@ -72,6 +78,12 @@ func (this *ProvinceManager) Start() {
} }
} }
func (this *ProvinceManager) Stop() {
if this.ticker != nil {
this.ticker.Stop()
}
}
func (this *ProvinceManager) Lookup(provinceName string) (provinceId int64) { func (this *ProvinceManager) Lookup(provinceName string) (provinceId int64) {
this.locker.RLock() this.locker.RLock()
provinceId, _ = this.provinceMap[provinceName] provinceId, _ = this.provinceMap[provinceName]
@@ -100,21 +112,25 @@ func (this *ProvinceManager) load() error {
return nil return nil
} }
// 更新国家信息 // 更新省份信息
func (this *ProvinceManager) loop() error { func (this *ProvinceManager) loop() error {
if this.isUpdated {
return nil
}
rpcClient, err := rpc.SharedRPC() rpcClient, err := rpc.SharedRPC()
if err != nil { if err != nil {
return err return err
} }
resp, err := rpcClient.RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(rpcClient.Context(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{ resp, err := rpcClient.RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(rpcClient.Context(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{
CountryId: ChinaCountryId, RegionCountryId: ChinaCountryId,
}) })
if err != nil { if err != nil {
return err return err
} }
m := map[string]int64{} m := map[string]int64{}
for _, province := range resp.Provinces { for _, province := range resp.RegionProvinces {
for _, code := range province.Codes { for _, code := range province.Codes {
m[code] = province.Id m[code] = province.Id
} }
@@ -135,6 +151,7 @@ func (this *ProvinceManager) loop() error {
this.locker.Lock() this.locker.Lock()
this.provinceMap = m this.provinceMap = m
this.isUpdated = true
this.locker.Unlock() this.locker.Unlock()
// 保存到本地缓存 // 保存到本地缓存

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/errors" "github.com/TeaOSLab/EdgeNode/internal/errors"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
@@ -14,15 +15,22 @@ import (
"time" "time"
) )
var SharedUpdater = NewUpdater()
func init() { func init() {
events.On(events.EventStart, func() { events.On(events.EventStart, func() {
updater := NewUpdater() goman.New(func() {
updater.Start() SharedUpdater.Start()
})
})
events.On(events.EventQuit, func() {
SharedUpdater.Stop()
}) })
} }
// Updater IP库更新程序 // Updater IP库更新程序
type Updater struct { type Updater struct {
ticker *time.Ticker
} }
// NewUpdater 获取新对象 // NewUpdater 获取新对象
@@ -33,15 +41,19 @@ func NewUpdater() *Updater {
// Start 开始更新 // Start 开始更新
func (this *Updater) Start() { func (this *Updater) Start() {
// 这里不需要太频繁检查更新因为通常不需要更新IP库 // 这里不需要太频繁检查更新因为通常不需要更新IP库
ticker := time.NewTicker(1 * time.Hour) this.ticker = time.NewTicker(1 * time.Hour)
go func() { for range this.ticker.C {
for range ticker.C { err := this.loop()
err := this.loop() if err != nil {
if err != nil { remotelogs.ErrorObject("IP_LIBRARY", err)
remotelogs.ErrorObject("IP_LIBRARY", err)
}
} }
}() }
}
func (this *Updater) Stop() {
if this.ticker != nil {
this.ticker.Stop()
}
} }
// 单次任务 // 单次任务

1
internal/js/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*

View File

@@ -1,50 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"encoding/json"
"github.com/iwind/TeaGo/logs"
"reflect"
)
type Console struct {
}
func (this *Console) Log(args ...interface{}) {
for index, arg := range args {
if arg != nil {
switch arg.(type) {
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string:
default:
var argType = reflect.TypeOf(arg)
// 是否有String()方法,如果有直接调用
method, ok := argType.MethodByName("String")
if ok && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).Kind() == reflect.String {
args[index] = method.Func.Call([]reflect.Value{reflect.ValueOf(arg)})[0].String()
continue
}
// 转为JSON
argJSON, err := this.toJSON(arg)
if err != nil {
if argType.Kind() == reflect.Func {
args[index] = "[function]"
} else {
args[index] = "[object]"
}
} else {
args[index] = string(argJSON)
}
}
} else {
args[index] = "null"
}
}
logs.Println(append([]interface{}{"[js][console]"}, args...)...)
}
func (this *Console) toJSON(o interface{}) ([]byte, error) {
return json.Marshal(o)
}

View File

@@ -1,38 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"testing"
)
func TestConsole_Log(t *testing.T) {
{
vm := NewVM()
_, err := vm.RunString("console.log('Hello', 'world')")
if err != nil {
t.Fatal(err)
}
}
{
vm := NewVM()
_, err := vm.RunString("console.log(null, true, false, 10, 10.123)")
if err != nil {
t.Fatal(err)
}
}
{
vm := NewVM()
_, err := vm.RunString("console.log({ a:1, b:2 })")
if err != nil {
t.Fatal(err)
}
}
{
vm := NewVM()
_, err := vm.RunString("console.log(console.log)")
if err != nil {
t.Fatal(err)
}
}
}

View File

@@ -1,36 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
type HTTP struct {
r RequestInterface
req *Request
resp *Response
onRequest func(req *Request, resp *Response)
}
func NewHTTP(r RequestInterface) *HTTP {
return &HTTP{
req: NewRequest(r),
resp: NewResponse(r),
}
}
func (this *HTTP) OnRequest(callback func(req *Request, resp *Response)) {
// TODO 考虑是否支持多个callback
this.onRequest = callback
}
func (this *HTTP) OnData(callback func(req *Request, resp *Response)) {
// TODO
}
func (this *HTTP) OnResponse(callback func(req *Request, resp *Response)) {
// TODO
}
func (this *HTTP) TriggerRequest() {
this.onRequest(this.req, this.resp)
}

View File

@@ -1,82 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"bytes"
"io/ioutil"
"net"
)
type Request struct {
r RequestInterface
}
func NewRequest(r RequestInterface) *Request {
return &Request{
r: r,
}
}
func (this *Request) Proto() string {
return this.r.JSRequest().Proto
}
func (this *Request) Method() string {
return this.r.JSRequest().Method
}
func (this *Request) Header() map[string][]string {
return this.r.JSRequest().Header
}
func (this *Request) AddHeader(name string, value string) {
this.r.JSRequest().Header[name] = append(this.r.JSRequest().Header[name], value)
}
func (this *Request) SetHeader(name string, value string) {
this.r.JSRequest().Header[name] = []string{value}
}
func (this *Request) RemoteAddr() string {
var remoteAddr = this.r.JSRequest().RemoteAddr
host, _, err := net.SplitHostPort(remoteAddr)
if err == nil {
return host
}
return remoteAddr
}
func (this *Request) Url() *URL {
return NewURL(this.r.JSRequest().URL)
}
func (this *Request) ContentLength() int64 {
return this.r.JSRequest().ContentLength
}
func (this *Request) Body() []byte {
var bodyReader = this.r.JSRequest().Body
if bodyReader == nil {
return []byte{}
}
data, err := ioutil.ReadAll(bodyReader)
if err != nil {
this.r.JSLog("read body failed: " + err.Error())
}
return data
}
func (this *Request) CopyBody() []byte {
var bodyReader = this.r.JSRequest().Body
if bodyReader == nil {
return []byte{}
}
data, err := ioutil.ReadAll(bodyReader)
if err != nil {
this.r.JSLog("read body failed: " + err.Error())
}
this.r.JSRequest().Body = ioutil.NopCloser(bytes.NewReader(data))
return data
}

View File

@@ -1,19 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import "net/http"
type RequestInterface interface {
// JSRequest 请求
JSRequest() *http.Request
// JSWriter 响应
JSWriter() http.ResponseWriter
// JSStop 中止请求
JSStop()
// JSLog 打印日志
JSLog(msg ...interface{})
}

View File

@@ -1,124 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js_test
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/js"
"github.com/iwind/TeaGo/logs"
"io/ioutil"
"net/http"
"testing"
)
type testRequest struct {
rawRequest *http.Request
rawResponse *testResponse
}
func (this *testRequest) JSRequest() *http.Request {
if this.rawRequest != nil {
return this.rawRequest
}
req, _ := http.NewRequest(http.MethodGet, "https://iwind:123456@goedge.cn/docs?name=Libai&age=20", nil)
req.Header.Set("Server", "edgejs/1.0")
req.Header.Set("Content-Type", "application/json")
req.Body = ioutil.NopCloser(bytes.NewReader([]byte("123456")))
this.rawRequest = req
return req
}
func (this *testRequest) JSWriter() http.ResponseWriter {
if this.rawResponse != nil {
return this.rawResponse
}
this.rawResponse = &testResponse{}
return this.rawResponse
}
func (this *testRequest) JSStop() {
}
func (this *testRequest) JSLog(msg ...interface{}) {
logs.Println(msg...)
}
type testResponse struct {
statusCode int
header http.Header
}
func (this *testResponse) Header() http.Header {
if this.header == nil {
this.header = http.Header{}
}
return this.header
}
func (this *testResponse) Write(p []byte) (int, error) {
return len(p), nil
}
func (this *testResponse) WriteHeader(statusCode int) {
this.statusCode = statusCode
}
func TestRequest(t *testing.T) {
vm := js.NewVM()
vm.SetRequest(&testRequest{})
// 事件监听
_, err := vm.RunString(`
http.onRequest(function (req, resp) {
console.log(req.proto())
let url = req.url()
console.log(url, "port:", url.port(), "args:", url.args())
console.log("username:", url.username(), "password:", url.password())
console.log("uri:", url.uri(), "path:", url.path())
req.addHeader("Server", "1.0")
resp.write("this is response")
console.log(resp)
console.log(req.body())
})
`)
if err != nil {
t.Fatal(err)
}
// 触发事件
_, err = vm.RunString(`http.triggerRequest()`)
if err != nil {
t.Fatal(err)
}
}
func TestRequest_Header(t *testing.T) {
var req = js.NewRequest(&testRequest{})
logs.PrintAsJSON(req.Header(), t)
req.AddHeader("Content-Length", "10")
req.AddHeader("Vary", "1.0")
req.AddHeader("Vary", "2.0")
logs.PrintAsJSON(req.Header(), t)
req.SetHeader("Vary", "3.0")
logs.PrintAsJSON(req.Header(), t)
}
func TestRequest_Body(t *testing.T) {
var req = js.NewRequest(&testRequest{})
t.Log(string(req.Body()))
t.Log(string(req.Body()))
}
func TestRequest_CopyBody(t *testing.T) {
var req = js.NewRequest(&testRequest{})
t.Log(string(req.CopyBody()))
t.Log(string(req.CopyBody()))
}

View File

@@ -1,39 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
type Response struct {
r RequestInterface
}
func NewResponse(r RequestInterface) *Response {
return &Response{
r: r,
}
}
func (this *Response) Write(s string) error {
_, err := this.r.JSWriter().Write([]byte(s))
return err
}
func (this *Response) Reply(status int) {
this.SetStatus(status)
this.r.JSStop()
}
func (this *Response) Header() map[string][]string {
return this.r.JSWriter().Header()
}
func (this *Response) AddHeader(name string, value string) {
this.r.JSWriter().Header()[name] = append(this.r.JSWriter().Header()[name], value)
}
func (this *Response) SetHeader(name string, value string) {
this.r.JSWriter().Header()[name] = []string{value}
}
func (this *Response) SetStatus(statusCode int) {
this.r.JSWriter().WriteHeader(statusCode)
}

View File

@@ -1,16 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
)
func TestNewResponse(t *testing.T) {
var resp = js.NewResponse(&testRequest{})
resp.AddHeader("Vary", "1.0")
resp.AddHeader("Vary", "2.0")
resp.SetHeader("Server", "edgejs/1.0")
t.Logf("%#v", resp.Header())
}

View File

@@ -1,90 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"github.com/dop251/goja"
"github.com/iwind/TeaGo/types"
"net/url"
)
type URL struct {
u *url.URL
}
func NewURL(u *url.URL) *URL {
return &URL{
u: u,
}
}
func (this *URL) JSNew(args []goja.Value) *URL {
var urlString = ""
if len(args) == 1 {
urlString = args[0].String()
}
u, _ := url.Parse(urlString)
if u == nil {
u = &url.URL{}
}
return NewURL(u)
}
func (this *URL) Port() int {
return types.Int(this.u.Port())
}
func (this *URL) Args() map[string][]string {
return this.u.Query()
}
func (this *URL) Arg(name string) string {
return this.u.Query().Get(name)
}
func (this *URL) Username() string {
if this.u.User != nil {
return this.u.User.Username()
}
return ""
}
func (this *URL) Password() string {
if this.u.User != nil {
password, _ := this.u.User.Password()
return password
}
return ""
}
func (this *URL) Uri() string {
return this.u.RequestURI()
}
func (this *URL) Path() string {
return this.u.Path
}
func (this *URL) Host() string {
return this.u.Host
}
func (this *URL) Fragment() string {
return this.u.Fragment
}
func (this *URL) Hash() string {
if len(this.u.Fragment) > 0 {
return "#" + this.u.Fragment
} else {
return ""
}
}
func (this *URL) Scheme() string {
return this.u.Scheme
}
func (this *URL) String() string {
return this.u.String()
}

View File

@@ -1,18 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"net/url"
"testing"
)
func TestURL(t *testing.T) {
raw, err := url.Parse("https://iwind:123456@goedge.cn/docs?name=Libai&age=20#a=b")
if err != nil {
t.Fatal(err)
}
var u = NewURL(raw)
t.Log("host:", u.Host())
t.Log("hash:", u.Hash())
}

View File

@@ -1,153 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"errors"
"github.com/dop251/goja"
"github.com/iwind/TeaGo/logs"
"reflect"
"strings"
)
var sharedPrograms []*goja.Program
var sharedConsole = &Console{}
func init() {
// compile programs
}
type VM struct {
vm *goja.Runtime
}
func NewVM() *VM {
vm := goja.New()
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
// programs
for _, program := range sharedPrograms {
_, _ = vm.RunProgram(program)
}
v := &VM{vm: vm}
v.initVM()
return v
}
func (this *VM) Set(name string, obj interface{}) error {
return this.vm.Set(name, obj)
}
func (this *VM) AddConstructor(name string, instance interface{}) error {
objType := reflect.TypeOf(instance)
if objType.Kind() != reflect.Ptr {
return errors.New("instance should be pointer")
}
// construct
newMethod, ok := objType.MethodByName("JSNew")
if !ok {
return errors.New("can not find 'JSNew()' method in '" + objType.Elem().Name() + "'")
}
var err = this.Set(name, func(call goja.ConstructorCall) *goja.Object {
if newMethod.Type.NumIn() != 2 {
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
return nil
}
if newMethod.Type.In(1).String() != "[]goja.Value" {
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
return nil
}
// new
var results = newMethod.Func.Call([]reflect.Value{reflect.ValueOf(instance), reflect.ValueOf(call.Arguments)})
if len(results) == 0 {
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a valid instance"))
return nil
}
var result = results[0]
if result.Type() != objType {
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a same instance"))
return nil
}
// methods
var resultType = result.Type()
var numMethod = result.NumMethod()
for i := 0; i < numMethod; i++ {
var method = resultType.Method(i)
var methodName = strings.ToLower(method.Name[:1]) + method.Name[1:]
err := call.This.Set(methodName, result.MethodByName(method.Name).Interface())
if err != nil {
this.throw(err)
continue
}
}
// 支持属性
var numField = result.Elem().Type().NumField()
for i := 0; i < numField; i++ {
var field = result.Elem().Field(i)
if !field.CanInterface() {
continue
}
var fieldType = objType.Elem().Field(i)
tag, ok := fieldType.Tag.Lookup("json")
if !ok {
tag = fieldType.Name
tag = strings.ToLower(tag[:1]) + tag[1:]
} else {
// TODO 校验tag是否符合变量语法
}
err := call.This.Set(tag, field.Interface())
if err != nil {
this.throw(err)
continue
}
}
return nil
})
return err
}
func (this *VM) RunString(str string) (goja.Value, error) {
defer func() {
e := recover()
if e != nil {
// TODO 需要打印trace
logs.Println("panic:", e)
}
}()
return this.vm.RunString(str)
}
func (this *VM) SetRequest(req RequestInterface) {
{
err := this.vm.Set("http", NewHTTP(req))
if err != nil {
this.throw(err)
}
}
}
func (this *VM) initVM() {
{
err := this.vm.Set("console", sharedConsole)
if err != nil {
this.throw(err)
}
}
}
func (this *VM) throw(err error) {
if err == nil {
return
}
// TODO
logs.Println("js:VM:error: " + err.Error())
}

View File

@@ -1,158 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"github.com/dop251/goja"
"testing"
"time"
)
func TestNewVM(t *testing.T) {
before := time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
vm := NewVM()
{
v, err := vm.RunString("JSON.stringify({\"a\":\"b\"})")
if err != nil {
t.Fatal(err)
}
t.Log("JSON.stringify():", v)
}
{
v, err := vm.RunString(`JSON.parse('{\"a\":\"b\"}')`)
if err != nil {
t.Fatal(err)
}
t.Log("JSON.parse():", v)
}
{
err := vm.AddConstructor("Url", &URL{})
if err != nil {
t.Fatal("add constructor error:", err)
}
_, err = vm.RunString(`
{
let u = new Url("https://goedge.cn/docs?v=1")
console.log("host:", u.host(), u.uri())
}
{
let u = new Url("https://teaos.cn/downloads?v=1")
console.log("host:", u.host(), u.uri())
}
{
let u = new Url()
console.log("host:", u.host(), u.uri())
}
{
let u = new Url("a", "b", "c")
console.log("host:", u.host(), u.uri())
}
`)
if err != nil {
t.Fatal("add constructor error:" + err.Error())
}
}
}
func TestVM_Program(t *testing.T) {
var s = `
{
let u = new Url("https://goedge.cn/docs?v=1")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("https://teaos.cn/downloads?v=1")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url()
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("a", "b", "c")
//console.log("host:", u.host(), u.uri())
}
`
program := goja.MustCompile("s", s, true)
before := time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
vm := NewVM()
err := vm.AddConstructor("Url", &URL{})
if err != nil {
t.Fatal("add constructor error:", err)
}
//_, err = vm.RunString(s)
_, err = vm.vm.RunProgram(program)
if err != nil {
t.Fatal("add constructor error:" + err.Error())
}
}
func Benchmark_Program(b *testing.B) {
var s = `
{
let u = new Url("https://goedge.cn/docs?v=1")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("https://teaos.cn/downloads?v=1")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url()
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("a", "b", "c")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("https://goedge.cn/docs?v=1")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("https://teaos.cn/downloads?v=1")
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url()
//console.log("host:", u.host(), u.uri())
}
{
let u = new Url("a", "b", "c")
//console.log("host:", u.host(), u.uri())
}
`
program := goja.MustCompile("s", s, true)
vm := NewVM()
err := vm.AddConstructor("Url", &URL{})
if err != nil {
b.Fatal("add constructor error:", err)
}
for i := 0; i < b.N; i++ {
//_, err = vm.RunString(s)
_, err = vm.vm.RunProgram(program)
if err != nil {
b.Fatal("add constructor error:" + err.Error())
}
}
}

View File

@@ -7,10 +7,12 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers" "github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"os" "os"
@@ -51,8 +53,8 @@ type Task struct {
selectTopStmt *sql.Stmt selectTopStmt *sql.Stmt
sumStmt *sql.Stmt sumStmt *sql.Stmt
serverIdMap map[int64]bool // 所有的服务Ids serverIdMap map[int64]zero.Zero // 所有的服务Ids
timeMap map[string]bool // time => bool timeMap map[string]zero.Zero // time => bool
serverIdMapLocker sync.Mutex serverIdMapLocker sync.Mutex
statsMap map[string]*Stat statsMap map[string]*Stat
@@ -64,8 +66,8 @@ type Task struct {
func NewTask(item *serverconfigs.MetricItemConfig) *Task { func NewTask(item *serverconfigs.MetricItemConfig) *Task {
return &Task{ return &Task{
item: item, item: item,
serverIdMap: map[int64]bool{}, serverIdMap: map[int64]zero.Zero{},
timeMap: map[string]bool{}, timeMap: map[string]zero.Zero{},
statsMap: map[string]*Stat{}, statsMap: map[string]*Stat{},
} }
} }
@@ -163,7 +165,7 @@ ON "` + this.statTableName + `" (
func (this *Task) Start() error { func (this *Task) Start() error {
// 读取数据 // 读取数据
this.statsTicker = utils.NewTicker(1 * time.Minute) this.statsTicker = utils.NewTicker(1 * time.Minute)
go func() { goman.New(func() {
for this.statsTicker.Next() { for this.statsTicker.Next() {
var tr = trackers.Begin("[METRIC]DUMP_STATS_TO_LOCAL_DATABASE") var tr = trackers.Begin("[METRIC]DUMP_STATS_TO_LOCAL_DATABASE")
@@ -181,11 +183,11 @@ func (this *Task) Start() error {
tr.End() tr.End()
} }
}() })
// 清理 // 清理
this.cleanTicker = utils.NewTicker(24 * time.Hour) this.cleanTicker = utils.NewTicker(24 * time.Hour)
go func() { goman.New(func() {
for this.cleanTicker.Next() { for this.cleanTicker.Next() {
var tr = trackers.Begin("[METRIC]CLEAN_EXPIRED") var tr = trackers.Begin("[METRIC]CLEAN_EXPIRED")
err := this.CleanExpired() err := this.CleanExpired()
@@ -194,11 +196,11 @@ func (this *Task) Start() error {
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error()) remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
} }
} }
}() })
// 上传 // 上传
this.uploadTicker = utils.NewTicker(this.item.UploadDuration()) this.uploadTicker = utils.NewTicker(this.item.UploadDuration())
go func() { goman.New(func() {
for this.uploadTicker.Next() { for this.uploadTicker.Next() {
var tr = trackers.Begin("[METRIC]UPLOAD_STATS") var tr = trackers.Begin("[METRIC]UPLOAD_STATS")
err := this.Upload(1 * time.Second) err := this.Upload(1 * time.Second)
@@ -207,7 +209,7 @@ func (this *Task) Start() error {
remotelogs.Error("METRIC", "upload stats failed: "+err.Error()) remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
} }
} }
}() })
return nil return nil
} }
@@ -293,8 +295,8 @@ func (this *Task) InsertStat(stat *Stat) error {
} }
this.serverIdMapLocker.Lock() this.serverIdMapLocker.Lock()
this.serverIdMap[stat.ServerId] = true this.serverIdMap[stat.ServerId] = zero.New()
this.timeMap[stat.Time] = true this.timeMap[stat.Time] = zero.New()
this.serverIdMapLocker.Unlock() this.serverIdMapLocker.Unlock()
keyData, err := json.Marshal(stat.Keys) keyData, err := json.Marshal(stat.Keys)
@@ -346,14 +348,14 @@ func (this *Task) Upload(pauseDuration time.Duration) error {
for serverId := range this.serverIdMap { for serverId := range this.serverIdMap {
serverIds = append(serverIds, serverId) serverIds = append(serverIds, serverId)
} }
this.serverIdMap = map[int64]bool{} // 清空数据 this.serverIdMap = map[int64]zero.Zero{} // 清空数据
// 时间 // 时间
var times = []string{} var times = []string{}
for t := range this.timeMap { for t := range this.timeMap {
times = append(times, t) times = append(times, t)
} }
this.timeMap = map[string]bool{} // 清空数据 this.timeMap = map[string]zero.Zero{} // 清空数据
this.serverIdMapLocker.Unlock() this.serverIdMapLocker.Unlock()
@@ -470,7 +472,7 @@ func (this *Task) loadServerIdMap() error {
return err return err
} }
this.serverIdMapLocker.Lock() this.serverIdMapLocker.Lock()
this.serverIdMap[serverId] = true this.serverIdMap[serverId] = zero.New()
this.serverIdMapLocker.Unlock() this.serverIdMapLocker.Unlock()
} }
} }
@@ -491,7 +493,7 @@ func (this *Task) loadServerIdMap() error {
return err return err
} }
this.serverIdMapLocker.Lock() this.serverIdMapLocker.Lock()
this.timeMap[timeString] = true this.timeMap[timeString] = zero.New()
this.serverIdMapLocker.Unlock() this.serverIdMapLocker.Unlock()
} }
} }

View File

@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -16,7 +17,9 @@ var SharedValueQueue = NewValueQueue()
func init() { func init() {
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
go SharedValueQueue.Start() goman.New(func() {
SharedValueQueue.Start()
})
}) })
} }

View File

@@ -13,6 +13,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/errors" "github.com/TeaOSLab/EdgeNode/internal/errors"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
@@ -30,6 +31,9 @@ import (
type APIStream struct { type APIStream struct {
stream pb.NodeService_NodeStreamClient stream pb.NodeService_NodeStreamClient
isQuiting bool
cancelFunc context.CancelFunc
} }
func NewAPIStream() *APIStream { func NewAPIStream() *APIStream {
@@ -37,12 +41,14 @@ func NewAPIStream() *APIStream {
} }
func (this *APIStream) Start() { func (this *APIStream) Start() {
isQuiting := false events.OnKey(events.EventQuit, this, func() {
events.On(events.EventQuit, func() { this.isQuiting = true
isQuiting = true if this.cancelFunc != nil {
this.cancelFunc()
}
}) })
for { for {
if isQuiting { if this.isQuiting {
return return
} }
err := this.loop() err := this.loop()
@@ -60,19 +66,17 @@ func (this *APIStream) loop() error {
if err != nil { if err != nil {
return errors.Wrap(err) return errors.Wrap(err)
} }
isQuiting := false
ctx, cancelFunc := context.WithCancel(rpcClient.Context())
nodeStream, err := rpcClient.NodeRPC().NodeStream(ctx)
events.On(events.EventQuit, func() {
isQuiting = true
remotelogs.Println("API_STREAM", "quiting") ctx, cancelFunc := context.WithCancel(rpcClient.Context())
if nodeStream != nil { this.cancelFunc = cancelFunc
cancelFunc()
} defer func() {
}) cancelFunc()
}()
nodeStream, err := rpcClient.NodeRPC().NodeStream(ctx)
if err != nil { if err != nil {
if isQuiting { if this.isQuiting {
return nil return nil
} }
return errors.Wrap(err) return errors.Wrap(err)
@@ -80,14 +84,14 @@ func (this *APIStream) loop() error {
this.stream = nodeStream this.stream = nodeStream
for { for {
if isQuiting { if this.isQuiting {
remotelogs.Println("API_STREAM", "quit") remotelogs.Println("API_STREAM", "quit")
break break
} }
message, err := nodeStream.Recv() message, err := nodeStream.Recv()
if err != nil { if err != nil {
if isQuiting { if this.isQuiting {
remotelogs.Println("API_STREAM", "quit") remotelogs.Println("API_STREAM", "quit")
return nil return nil
} }
@@ -139,15 +143,11 @@ func (this *APIStream) handleConnectedAPINode(message *pb.NodeStreamMessage) err
return errors.Wrap(err) return errors.Wrap(err)
} }
rpcClient, err := rpc.SharedRPC() _, err = rpc.SharedRPC()
if err != nil { if err != nil {
return errors.Wrap(err) return errors.Wrap(err)
} }
_, err = rpcClient.NodeRPC().UpdateNodeConnectedAPINodes(rpcClient.Context(), &pb.UpdateNodeConnectedAPINodesRequest{ApiNodeIds: []int64{msg.APINodeId}})
if err != nil {
return errors.Wrap(err)
}
remotelogs.Println("API_STREAM", "connected to api node '"+strconv.FormatInt(msg.APINodeId, 10)+"'") remotelogs.Println("API_STREAM", "connected to api node '"+strconv.FormatInt(msg.APINodeId, 10)+"'")
// 重新读取配置 // 重新读取配置
@@ -182,7 +182,7 @@ func (this *APIStream) handleWriteCache(message *pb.NodeStreamMessage) error {
} }
expiredAt := time.Now().Unix() + msg.LifeSeconds expiredAt := time.Now().Unix() + msg.LifeSeconds
writer, err := storage.OpenWriter(msg.Key, expiredAt, 200) writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, int64(len(msg.Value)), false)
if err != nil { if err != nil {
this.replyFail(message.RequestId, "prepare writing failed: "+err.Error()) this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
return err return err
@@ -239,7 +239,7 @@ func (this *APIStream) handleReadCache(message *pb.NodeStreamMessage) error {
}() }()
} }
reader, err := storage.OpenReader(msg.Key) reader, err := storage.OpenReader(msg.Key, false)
if err != nil { if err != nil {
if err == caches.ErrNotFound { if err == caches.ErrNotFound {
this.replyFail(message.RequestId, "key not found") this.replyFail(message.RequestId, "key not found")
@@ -462,7 +462,7 @@ func (this *APIStream) handlePreheatCache(message *pb.NodeStreamMessage) error {
} }
expiredAt := time.Now().Unix() + 8600 expiredAt := time.Now().Unix() + 8600
writer, err := storage.OpenWriter(key, expiredAt, 200) // TODO 可以设置缓存过期时间 writer, err := storage.OpenWriter(key, expiredAt, 200, resp.ContentLength, false) // TODO 可以设置缓存过期时间
if err != nil { if err != nil {
locker.Lock() locker.Lock()
errorMessages = append(errorMessages, "open cache writer failed: "+key+": "+err.Error()) errorMessages = append(errorMessages, "open cache writer failed: "+key+": "+err.Error())
@@ -607,7 +607,7 @@ func (this *APIStream) handleChangeAPINode(message *pb.NodeStreamMessage) error
this.replyOk(message.RequestId, "") this.replyOk(message.RequestId, "")
go func() { goman.New(func() {
// 延后生效防止变更前的API无法读取到状态 // 延后生效防止变更前的API无法读取到状态
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -629,7 +629,7 @@ func (this *APIStream) handleChangeAPINode(message *pb.NodeStreamMessage) error
remotelogs.Println("API_STREAM", "change rpc endpoint to '"+ remotelogs.Println("API_STREAM", "change rpc endpoint to '"+
messageData.Addr+"' successfully") messageData.Addr+"' successfully")
}() })
return nil return nil
} }

View File

@@ -4,64 +4,82 @@ package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/monitor" "github.com/TeaOSLab/EdgeNode/internal/ratelimit"
"github.com/iwind/TeaGo/maps" "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"
"os"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
) )
// 发送监控流量
func init() {
events.On(events.EventStart, func() {
ticker := time.NewTicker(1 * time.Minute)
go func() {
for range ticker.C {
// 加入到数据队列中
if teaconst.InTrafficBytes > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficIn, maps.Map{
"total": teaconst.InTrafficBytes,
})
}
if teaconst.OutTrafficBytes > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficOut, maps.Map{
"total": teaconst.OutTrafficBytes,
})
}
// 重置数据
atomic.StoreUint64(&teaconst.InTrafficBytes, 0)
atomic.StoreUint64(&teaconst.OutTrafficBytes, 0)
}
}()
})
}
// ClientConn 客户端连接 // ClientConn 客户端连接
type ClientConn struct { type ClientConn struct {
rawConn net.Conn once sync.Once
isClosed bool globalLimiter *ratelimit.Counter
isTLS bool
hasDeadline bool
hasRead bool
hasResetSYNFlood bool
BaseClientConn
} }
func NewClientConn(conn net.Conn, quickClose bool) net.Conn { func NewClientConn(conn net.Conn, isTLS bool, quickClose bool, globalLimiter *ratelimit.Counter) net.Conn {
if quickClose { if quickClose {
// TCP
tcpConn, ok := conn.(*net.TCPConn) tcpConn, ok := conn.(*net.TCPConn)
if ok { if ok {
// TODO 可以设置此值 // TODO 可以在配置中设置此值
_ = tcpConn.SetLinger(3) _ = tcpConn.SetLinger(nodeconfigs.DefaultTCPLinger)
} }
} }
return &ClientConn{rawConn: conn} return &ClientConn{BaseClientConn: BaseClientConn{rawConn: conn}, isTLS: isTLS, globalLimiter: globalLimiter}
} }
func (this *ClientConn) Read(b []byte) (n int, err error) { func (this *ClientConn) Read(b []byte) (n int, err error) {
if this.isTLS {
if !this.hasDeadline {
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
this.hasDeadline = true
defer func() {
_ = this.rawConn.SetReadDeadline(time.Time{})
}()
}
}
n, err = this.rawConn.Read(b) n, err = this.rawConn.Read(b)
if n > 0 { if n > 0 {
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n)) atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
if !this.hasRead {
this.hasRead = true
}
} }
// SYN Flood检测
var isHandshakeError = err != nil && os.IsTimeout(err) && !this.hasRead
if isHandshakeError {
_ = this.SetLinger(0)
}
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
if synFloodConfig != nil && synFloodConfig.IsOn {
if isHandshakeError {
this.increaseSYNFlood(synFloodConfig)
} else if err == nil && !this.hasResetSYNFlood {
this.hasResetSYNFlood = true
this.resetSYNFlood()
}
}
return return
} }
@@ -75,7 +93,20 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
func (this *ClientConn) Close() error { func (this *ClientConn) Close() error {
this.isClosed = true this.isClosed = true
return this.rawConn.Close()
err := this.rawConn.Close()
// 全局并发数限制
this.once.Do(func() {
if this.globalLimiter != nil {
this.globalLimiter.Release()
}
})
// 单个服务并发数限制
sharedClientConnLimiter.Remove(this.rawConn.RemoteAddr().String())
return err
} }
func (this *ClientConn) LocalAddr() net.Addr { func (this *ClientConn) LocalAddr() net.Addr {
@@ -98,6 +129,29 @@ func (this *ClientConn) SetWriteDeadline(t time.Time) error {
return this.rawConn.SetWriteDeadline(t) return this.rawConn.SetWriteDeadline(t)
} }
func (this *ClientConn) IsClosed() bool { func (this *ClientConn) resetSYNFlood() {
return this.isClosed ttlcache.SharedCache.Delete("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 timestamp = utils.NextMinuteUnixTime()
var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp)
var minAttempts = synFloodConfig.MinAttempts
if minAttempts < 5 {
minAttempts = 5
}
if !this.isTLS {
// 非TLS设置为两倍防止误封
minAttempts = 2 * minAttempts
}
if result >= int64(minAttempts) {
var timeout = synFloodConfig.TimeoutSeconds
if timeout <= 0 {
timeout = 600
}
waf.SharedIPBlackList.RecordIP(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip, time.Now().Unix()+int64(timeout), 0, true, 0, 0, "疑似SYN Flood攻击当前1分钟"+types.String(result)+"次空连接")
}
}
} }

View File

@@ -0,0 +1,58 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import "net"
type BaseClientConn struct {
rawConn net.Conn
isBound bool
serverId int64
remoteAddr string
isClosed bool
}
func (this *BaseClientConn) IsClosed() bool {
return this.isClosed
}
// IsBound 是否已绑定服务
func (this *BaseClientConn) IsBound() bool {
return this.isBound
}
// Bind 绑定服务
func (this *BaseClientConn) Bind(serverId int64, remoteAddr string, maxConnsPerServer int, maxConnsPerIP int) bool {
if this.isBound {
return true
}
this.isBound = true
this.serverId = serverId
this.remoteAddr = remoteAddr
// 检查是否可以连接
return sharedClientConnLimiter.Add(this.rawConn.RemoteAddr().String(), serverId, remoteAddr, maxConnsPerServer, maxConnsPerIP)
}
// RawIP 原本IP
func (this *BaseClientConn) RawIP() string {
ip, _, _ := net.SplitHostPort(this.rawConn.RemoteAddr().String())
return ip
}
// TCPConn 转换为TCPConn
func (this *BaseClientConn) TCPConn() (*net.TCPConn, bool) {
conn, ok := this.rawConn.(*net.TCPConn)
return conn, ok
}
// SetLinger 设置Linger
func (this *BaseClientConn) SetLinger(seconds int) error {
tcpConn, ok := this.TCPConn()
if ok {
return tcpConn.SetLinger(seconds)
}
return nil
}

View File

@@ -0,0 +1,14 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
type ClientConnInterface interface {
// IsClosed 是否已关闭
IsClosed() bool
// IsBound 是否已绑定服务
IsBound() bool
// Bind 绑定服务
Bind(serverId int64, remoteAddr string, maxConnsPerServer int, maxConnsPerIP int) bool
}

View File

@@ -0,0 +1,130 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"sync"
)
var sharedClientConnLimiter = NewClientConnLimiter()
// ClientConnRemoteAddr 客户端地址定义
type ClientConnRemoteAddr struct {
remoteAddr string
serverId int64
}
// ClientConnLimiter 客户端连接数限制
type ClientConnLimiter struct {
remoteAddrMap map[string]*ClientConnRemoteAddr // raw remote addr => remoteAddr
ipConns map[string]map[string]zero.Zero // remoteAddr => { raw remote addr => Zero }
serverConns map[int64]map[string]zero.Zero // serverId => { remoteAddr => Zero }
locker sync.Mutex
}
func NewClientConnLimiter() *ClientConnLimiter {
return &ClientConnLimiter{
remoteAddrMap: map[string]*ClientConnRemoteAddr{},
ipConns: map[string]map[string]zero.Zero{},
serverConns: map[int64]map[string]zero.Zero{},
}
}
// Add 添加新连接
// 返回值为true的时候表示允许添加否则表示不允许添加
func (this *ClientConnLimiter) Add(rawRemoteAddr string, serverId int64, remoteAddr string, maxConnsPerServer int, maxConnsPerIP int) bool {
if (maxConnsPerServer <= 0 && maxConnsPerIP <= 0) || len(remoteAddr) == 0 || serverId <= 0 {
return true
}
this.locker.Lock()
defer this.locker.Unlock()
// 检查服务连接数
var serverMap = this.serverConns[serverId]
if maxConnsPerServer > 0 {
if serverMap == nil {
serverMap = map[string]zero.Zero{}
this.serverConns[serverId] = serverMap
}
if maxConnsPerServer <= len(serverMap) {
return false
}
}
// 检查IP连接数
var ipMap = this.ipConns[remoteAddr]
if maxConnsPerIP > 0 {
if ipMap == nil {
ipMap = map[string]zero.Zero{}
this.ipConns[remoteAddr] = ipMap
}
if maxConnsPerIP > 0 && maxConnsPerIP <= len(ipMap) {
return false
}
}
this.remoteAddrMap[rawRemoteAddr] = &ClientConnRemoteAddr{
remoteAddr: remoteAddr,
serverId: serverId,
}
if maxConnsPerServer > 0 {
serverMap[rawRemoteAddr] = zero.New()
}
if maxConnsPerIP > 0 {
ipMap[rawRemoteAddr] = zero.New()
}
return true
}
// Remove 删除连接
func (this *ClientConnLimiter) Remove(rawRemoteAddr string) {
this.locker.Lock()
defer this.locker.Unlock()
addr, ok := this.remoteAddrMap[rawRemoteAddr]
if !ok {
return
}
delete(this.remoteAddrMap, rawRemoteAddr)
delete(this.ipConns[addr.remoteAddr], rawRemoteAddr)
delete(this.serverConns[addr.serverId], rawRemoteAddr)
if len(this.ipConns[addr.remoteAddr]) == 0 {
delete(this.ipConns, addr.remoteAddr)
}
if len(this.serverConns[addr.serverId]) == 0 {
delete(this.serverConns, addr.serverId)
}
}
// Conns 获取连接信息
// 用于调试
func (this *ClientConnLimiter) Conns() (ipConns map[string][]string, serverConns map[int64][]string) {
this.locker.Lock()
defer this.locker.Unlock()
ipConns = map[string][]string{} // ip => [addr1, addr2, ...]
serverConns = map[int64][]string{} // serverId => [addr1, addr2, ...]
for ip, m := range this.ipConns {
for addr := range m {
ipConns[ip] = append(ipConns[ip], addr)
}
}
for serverId, m := range this.serverConns {
for addr := range m {
serverConns[serverId] = append(serverConns[serverId], addr)
}
}
return
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestClientConnLimiter_Add(t *testing.T) {
var limiter = NewClientConnLimiter()
{
b := limiter.Add("127.0.0.1:1234", 1, "192.168.1.100", 10, 5)
t.Log(b)
}
{
b := limiter.Add("127.0.0.1:1235", 1, "192.168.1.100", 10, 5)
t.Log(b)
}
{
b := limiter.Add("127.0.0.1:1236", 1, "192.168.1.100", 10, 5)
t.Log(b)
}
{
b := limiter.Add("127.0.0.1:1237", 1, "192.168.1.101", 10, 5)
t.Log(b)
}
{
b := limiter.Add("127.0.0.1:1238", 1, "192.168.1.100", 5, 5)
t.Log(b)
}
limiter.Remove("127.0.0.1:1238")
limiter.Remove("127.0.0.1:1239")
limiter.Remove("127.0.0.1:1237")
logs.PrintAsJSON(limiter.remoteAddrMap, t)
logs.PrintAsJSON(limiter.ipConns, t)
logs.PrintAsJSON(limiter.serverConns, t)
}

View File

@@ -0,0 +1,40 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/monitor"
"github.com/iwind/TeaGo/maps"
"sync/atomic"
"time"
)
// 发送监控流量
func init() {
events.On(events.EventStart, func() {
var ticker = time.NewTicker(1 * time.Minute)
goman.New(func() {
for range ticker.C {
// 加入到数据队列中
if teaconst.InTrafficBytes > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficIn, maps.Map{
"total": teaconst.InTrafficBytes,
})
}
if teaconst.OutTrafficBytes > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficOut, maps.Map{
"total": teaconst.OutTrafficBytes,
})
}
// 重置数据
atomic.StoreUint64(&teaconst.InTrafficBytes, 0)
atomic.StoreUint64(&teaconst.OutTrafficBytes, 0)
}
})
})
}

View File

@@ -11,12 +11,10 @@ func isClientConnClosed(conn net.Conn) bool {
if conn == nil { if conn == nil {
return true return true
} }
clientConn, ok := conn.(*ClientConn) clientConn, ok := conn.(ClientConnInterface)
if ok { if ok {
return clientConn.IsClosed() return clientConn.IsClosed()
} }
// TODO 解决tls.Conn无法获取底层连接对象的问题 return true
return false
} }

View File

@@ -3,30 +3,49 @@
package nodes package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary" "github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/ratelimit"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"net" "net"
) )
var sharedConnectionsLimiter = ratelimit.NewCounter(nodeconfigs.DefaultTCPMaxConnections)
// ClientListener 客户端网络监听 // ClientListener 客户端网络监听
type ClientListener struct { type ClientListener struct {
rawListener net.Listener rawListener net.Listener
isTLS bool
quickClose bool quickClose bool
} }
func NewClientListener(listener net.Listener, quickClose bool) net.Listener { func NewClientListener(listener net.Listener, quickClose bool) *ClientListener {
return &ClientListener{ return &ClientListener{
rawListener: listener, rawListener: listener,
quickClose: quickClose, quickClose: quickClose,
} }
} }
func (this *ClientListener) SetIsTLS(isTLS bool) {
this.isTLS = isTLS
}
func (this *ClientListener) IsTLS() bool {
return this.isTLS
}
func (this *ClientListener) Accept() (net.Conn, error) { func (this *ClientListener) Accept() (net.Conn, error) {
// 限制并发连接数
var limiter = sharedConnectionsLimiter
limiter.Ack()
conn, err := this.rawListener.Accept() conn, err := this.rawListener.Accept()
if err != nil { if err != nil {
limiter.Release()
return nil, err return nil, err
} }
// 是否在WAF名单中 // 是否在WAF名单中
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String()) ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
if err == nil { if err == nil {
@@ -38,11 +57,12 @@ func (this *ClientListener) Accept() (net.Conn, error) {
} }
_ = conn.Close() _ = conn.Close()
limiter.Release()
return this.Accept() return this.Accept()
} }
} }
return NewClientConn(conn, this.quickClose), nil return NewClientConn(conn, this.isTLS, this.quickClose, limiter), nil
} }
func (this *ClientListener) Close() error { func (this *ClientListener) Close() error {

View File

@@ -0,0 +1,57 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"crypto/tls"
"net"
"time"
)
// ClientTLSConn TLS连接封装
type ClientTLSConn struct {
BaseClientConn
}
func NewClientTLSConn(conn *tls.Conn) net.Conn {
return &ClientTLSConn{BaseClientConn{rawConn: conn}}
}
func (this *ClientTLSConn) Read(b []byte) (n int, err error) {
n, err = this.rawConn.Read(b)
return
}
func (this *ClientTLSConn) Write(b []byte) (n int, err error) {
n, err = this.rawConn.Write(b)
return
}
func (this *ClientTLSConn) Close() error {
this.isClosed = true
// 单个服务并发数限制
sharedClientConnLimiter.Remove(this.rawConn.RemoteAddr().String())
return this.rawConn.Close()
}
func (this *ClientTLSConn) LocalAddr() net.Addr {
return this.rawConn.LocalAddr()
}
func (this *ClientTLSConn) RemoteAddr() net.Addr {
return this.rawConn.RemoteAddr()
}
func (this *ClientTLSConn) SetDeadline(t time.Time) error {
return this.rawConn.SetDeadline(t)
}
func (this *ClientTLSConn) SetReadDeadline(t time.Time) error {
return this.rawConn.SetReadDeadline(t)
}
func (this *ClientTLSConn) SetWriteDeadline(t time.Time) error {
return this.rawConn.SetWriteDeadline(t)
}

View File

@@ -1,11 +1,13 @@
package nodes package nodes
import ( import (
"bytes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"strconv" "strings"
"time" "time"
) )
@@ -26,7 +28,9 @@ func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
queue := &HTTPAccessLogQueue{ queue := &HTTPAccessLogQueue{
queue: make(chan *pb.HTTPAccessLog, maxSize), queue: make(chan *pb.HTTPAccessLog, maxSize),
} }
go queue.Start() goman.New(func() {
queue.Start()
})
return queue return queue
} }
@@ -55,24 +59,11 @@ func (this *HTTPAccessLogQueue) Push(accessLog *pb.HTTPAccessLog) {
func (this *HTTPAccessLogQueue) loop() error { func (this *HTTPAccessLogQueue) loop() error {
var accessLogs = []*pb.HTTPAccessLog{} var accessLogs = []*pb.HTTPAccessLog{}
var count = 0 var count = 0
var timestamp int64
var requestId = 1_000_000
Loop: Loop:
for { for {
select { select {
case accessLog := <-this.queue: case accessLog := <-this.queue:
var unixTime = utils.UnixTime()
if unixTime > timestamp {
requestId = 1_000_000
timestamp = unixTime
} else {
requestId++
}
// timestamp + requestId + nodeId
accessLog.RequestId = strconv.FormatInt(unixTime, 10) + strconv.Itoa(requestId) + strconv.FormatInt(accessLog.NodeId, 10)
accessLogs = append(accessLogs, accessLog) accessLogs = append(accessLogs, accessLog)
count++ count++
@@ -100,8 +91,51 @@ Loop:
_, err := this.rpcClient.HTTPAccessLogRPC().CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs}) _, err := this.rpcClient.HTTPAccessLogRPC().CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
if err != nil { if err != nil {
// 是否包含了invalid UTF-8
if strings.Contains(err.Error(), "string field contains invalid UTF-8") {
for _, accessLog := range accessLogs {
this.toValidUTF8(accessLog)
}
// 重新提交
_, err = this.rpcClient.HTTPAccessLogRPC().CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
return err
}
return err return err
} }
return nil return nil
} }
func (this *HTTPAccessLogQueue) toValidUTF8(accessLog *pb.HTTPAccessLog) {
accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser)
accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI)
accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath)
accessLog.RequestFilename = utils.ToValidUTF8string(accessLog.RequestFilename)
accessLog.RequestBody = bytes.ToValidUTF8(accessLog.RequestBody, []byte{})
for _, v := range accessLog.SentHeader {
for index, s := range v.Values {
v.Values[index] = utils.ToValidUTF8string(s)
}
}
accessLog.Referer = utils.ToValidUTF8string(accessLog.Referer)
accessLog.UserAgent = utils.ToValidUTF8string(accessLog.UserAgent)
accessLog.Request = utils.ToValidUTF8string(accessLog.Request)
accessLog.ContentType = utils.ToValidUTF8string(accessLog.ContentType)
for k, c := range accessLog.Cookie {
accessLog.Cookie[k] = utils.ToValidUTF8string(c)
}
accessLog.Args = utils.ToValidUTF8string(accessLog.Args)
accessLog.QueryString = utils.ToValidUTF8string(accessLog.QueryString)
for _, v := range accessLog.Header {
for index, s := range v.Values {
v.Values[index] = utils.ToValidUTF8string(s)
}
}
}

View File

@@ -0,0 +1,133 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"bytes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
_ "github.com/iwind/TeaGo/bootstrap"
"google.golang.org/grpc/status"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
func TestHTTPAccessLogQueue_Push(t *testing.T) {
// 发送到API
client, err := rpc.SharedRPC()
if err != nil {
t.Fatal(err)
}
var requestId = 1_000_000
var utf8Bytes = []byte{}
for i := 0; i < 254; i++ {
utf8Bytes = append(utf8Bytes, uint8(i))
}
//bytes = []byte("真不错")
var accessLog = &pb.HTTPAccessLog{
ServerId: 23,
RequestId: strconv.FormatInt(time.Now().Unix(), 10) + strconv.Itoa(requestId) + strconv.FormatInt(1, 10),
NodeId: 48,
Host: "www.hello.com",
RequestURI: string(utf8Bytes),
RequestPath: string(utf8Bytes),
Timestamp: time.Now().Unix(),
Cookie: map[string]string{"test": string(utf8Bytes)},
Header: map[string]*pb.Strings{
"test": {Values: []string{string(utf8Bytes)}},
},
}
new(HTTPAccessLogQueue).toValidUTF8(accessLog)
// logs.PrintAsJSON(accessLog)
//t.Log(strings.ToValidUTF8(string(utf8Bytes), ""))
_, err = client.HTTPAccessLogRPC().CreateHTTPAccessLogs(client.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: []*pb.HTTPAccessLog{
accessLog,
}})
if err != nil {
// 这里只是为了重现错误
t.Logf("%#v, %s", err, err.Error())
statusErr, ok := status.FromError(err)
if ok {
t.Logf("%#v", statusErr)
}
return
}
t.Log("ok")
}
func TestHTTPAccessLogQueue_Push2(t *testing.T) {
var utf8Bytes = []byte{}
for i := 0; i < 254; i++ {
utf8Bytes = append(utf8Bytes, uint8(i))
}
var accessLog = &pb.HTTPAccessLog{
ServerId: 23,
RequestId: strconv.FormatInt(time.Now().Unix(), 10) + strconv.Itoa(1) + strconv.FormatInt(1, 10),
NodeId: 48,
Host: "www.hello.com",
RequestURI: string(utf8Bytes),
RequestPath: string(utf8Bytes),
Timestamp: time.Now().Unix(),
}
var v = reflect.Indirect(reflect.ValueOf(accessLog))
var countFields = v.NumField()
for i := 0; i < countFields; i++ {
var field = v.Field(i)
if field.Kind() == reflect.String {
field.SetString(strings.ToValidUTF8(field.String(), ""))
}
}
client, err := rpc.SharedRPC()
if err != nil {
t.Fatal(err)
}
_, err = client.HTTPAccessLogRPC().CreateHTTPAccessLogs(client.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: []*pb.HTTPAccessLog{
accessLog,
}})
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func BenchmarkHTTPAccessLogQueue_ToValidUTF8(b *testing.B) {
runtime.GOMAXPROCS(1)
var utf8Bytes = []byte{}
for i := 0; i < 254; i++ {
utf8Bytes = append(utf8Bytes, uint8(i))
}
for i := 0; i < b.N; i++ {
_ = bytes.ToValidUTF8(utf8Bytes, nil)
}
}
func BenchmarkHTTPAccessLogQueue_ToValidUTF8String(b *testing.B) {
runtime.GOMAXPROCS(1)
var utf8Bytes = []byte{}
for i := 0; i < 254; i++ {
utf8Bytes = append(utf8Bytes, uint8(i))
}
var s = string(utf8Bytes)
for i := 0; i < b.N; i++ {
_ = strings.ToValidUTF8(s, "")
}
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"net" "net"
@@ -33,7 +34,9 @@ func NewHTTPClientPool() *HTTPClientPool {
clientsMap: map[string]*HTTPClient{}, clientsMap: map[string]*HTTPClient{},
} }
go pool.cleanClients() goman.New(func() {
pool.cleanClients()
})
return pool return pool
} }
@@ -81,14 +84,13 @@ func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.Origi
if idleConns <= 0 { if idleConns <= 0 {
idleConns = numberCPU * 8 idleConns = numberCPU * 8
} }
//logs.Println("[ORIGIN]max connections:", maxConnections)
// TLS通讯 // TLS通讯
tlsConfig := &tls.Config{ var tlsConfig = &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
} }
if origin.Cert != nil { if origin.Cert != nil {
obj := origin.Cert.CertObject() var obj = origin.Cert.CertObject()
if obj != nil { if obj != nil {
tlsConfig.InsecureSkipVerify = false tlsConfig.InsecureSkipVerify = false
tlsConfig.Certificates = []tls.Certificate{*obj} tlsConfig.Certificates = []tls.Certificate{*obj}
@@ -98,37 +100,16 @@ func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.Origi
} }
} }
transport := &http.Transport{ var transport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 支持TOA的连接 // 支持TOA的连接
toaConfig := sharedTOAManager.Config() conn, err := this.handleTOA(req, ctx, network, originAddr, connectionTimeout)
if toaConfig != nil && toaConfig.IsOn { if conn != nil || err != nil {
retries := 3 return conn, err
for i := 1; i <= retries; i++ {
port := int(toaConfig.RandLocalPort())
// TODO 思考是否支持X-Real-IP/X-Forwarded-IP
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + req.requestRemoteAddr(true))
if err != nil {
remotelogs.Error("TOA", "add failed: "+err.Error())
} else {
dialer := net.Dialer{
Timeout: connectionTimeout,
KeepAlive: 1 * time.Minute,
LocalAddr: &net.TCPAddr{
Port: port,
},
}
conn, err := dialer.DialContext(ctx, network, originAddr)
// TODO 需要在合适的时机删除TOA记录
if err == nil || i == retries {
return conn, err
}
}
}
} }
// 普通的连接 // 普通的连接
conn, err := (&net.Dialer{ conn, err = (&net.Dialer{
Timeout: connectionTimeout, Timeout: connectionTimeout,
KeepAlive: 1 * time.Minute, KeepAlive: 1 * time.Minute,
}).DialContext(ctx, network, originAddr) }).DialContext(ctx, network, originAddr)
@@ -136,32 +117,10 @@ func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.Origi
return nil, err return nil, err
} }
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) { // 处理PROXY protocol
var remoteAddr = req.requestRemoteAddr(true) err = this.handlePROXYProtocol(conn, req, proxyProtocol)
var transportProtocol = proxyproto.TCPv4 if err != nil {
if strings.Contains(remoteAddr, ":") { return nil, err
transportProtocol = proxyproto.TCPv6
}
var destAddr = conn.RemoteAddr()
var reqConn = req.RawReq.Context().Value(HTTPConnContextKey)
if reqConn != nil {
destAddr = reqConn.(net.Conn).LocalAddr()
}
header := proxyproto.Header{
Version: byte(proxyProtocol.Version),
Command: proxyproto.PROXY,
TransportProtocol: transportProtocol,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP(remoteAddr),
Port: req.requestRemotePort(),
},
DestinationAddr: destAddr,
}
_, err = header.WriteTo(conn)
if err != nil {
_ = conn.Close()
return nil, err
}
} }
return conn, nil return conn, nil
@@ -171,7 +130,7 @@ func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.Origi
MaxConnsPerHost: maxConnections, MaxConnsPerHost: maxConnections,
IdleConnTimeout: idleTimeout, IdleConnTimeout: idleTimeout,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
TLSHandshakeTimeout: 0, // 不限 TLSHandshakeTimeout: 3 * time.Second,
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
Proxy: nil, Proxy: nil,
} }
@@ -205,3 +164,69 @@ func (this *HTTPClientPool) cleanClients() {
this.locker.Unlock() this.locker.Unlock()
} }
} }
// 支持TOA
func (this *HTTPClientPool) handleTOA(req *HTTPRequest, ctx context.Context, network string, originAddr string, connectionTimeout time.Duration) (net.Conn, error) {
// TODO 每个服务读取自身所属集群的TOA设置
toaConfig := sharedTOAManager.Config()
if toaConfig != nil && toaConfig.IsOn {
retries := 3
for i := 1; i <= retries; i++ {
port := int(toaConfig.RandLocalPort())
// TODO 思考是否支持X-Real-IP/X-Forwarded-IP
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + req.requestRemoteAddr(true))
if err != nil {
remotelogs.Error("TOA", "add failed: "+err.Error())
} else {
dialer := net.Dialer{
Timeout: connectionTimeout,
KeepAlive: 1 * time.Minute,
LocalAddr: &net.TCPAddr{
Port: port,
},
}
conn, err := dialer.DialContext(ctx, network, originAddr)
// TODO 需要在合适的时机删除TOA记录
if err == nil || i == retries {
return conn, err
}
}
}
}
return nil, nil
}
// 支持PROXY Protocol
func (this *HTTPClientPool) handlePROXYProtocol(conn net.Conn, req *HTTPRequest, proxyProtocol *serverconfigs.ProxyProtocolConfig) error {
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
var remoteAddr = req.requestRemoteAddr(true)
var transportProtocol = proxyproto.TCPv4
if strings.Contains(remoteAddr, ":") {
transportProtocol = proxyproto.TCPv6
}
var destAddr = conn.RemoteAddr()
var reqConn = req.RawReq.Context().Value(HTTPConnContextKey)
if reqConn != nil {
destAddr = reqConn.(net.Conn).LocalAddr()
}
header := proxyproto.Header{
Version: byte(proxyProtocol.Version),
Command: proxyproto.PROXY,
TransportProtocol: transportProtocol,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP(remoteAddr),
Port: req.requestRemotePort(),
},
DestinationAddr: destAddr,
}
_, err := header.WriteTo(conn)
if err != nil {
_ = conn.Close()
return err
}
return nil
}
return nil
}

View File

@@ -1,17 +1,22 @@
package nodes package nodes
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/metrics" "github.com/TeaOSLab/EdgeNode/internal/metrics"
"github.com/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@@ -25,22 +30,18 @@ import (
// 环境变量 // 环境变量
var HOSTNAME, _ = os.Hostname() var HOSTNAME, _ = os.Hostname()
// byte pool
var bytePool256b = utils.NewBytePool(20480, 256)
var bytePool1k = utils.NewBytePool(20480, 1024)
var bytePool32k = utils.NewBytePool(20480, 32*1024)
var bytePool128k = utils.NewBytePool(20480, 128*1024)
// errors // errors
var errWritingToClient = errors.New("writing to client error") var errWritingToClient = errors.New("writing to client error")
// HTTPRequest HTTP请求 // HTTPRequest HTTP请求
type HTTPRequest struct { type HTTPRequest struct {
requestId string
// 外部参数 // 外部参数
RawReq *http.Request RawReq *http.Request
RawWriter http.ResponseWriter RawWriter http.ResponseWriter
Server *serverconfigs.ServerConfig ReqServer *serverconfigs.ServerConfig
Host string // 请求的Host ReqHost string // 请求的Host
ServerName string // 实际匹配到的Host ServerName string // 实际匹配到的Host
ServerAddr string // 实际启动的服务器监听地址 ServerAddr string // 实际启动的服务器监听地址
IsHTTP bool IsHTTP bool
@@ -64,11 +65,15 @@ type HTTPRequest struct {
rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则 rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则
rewriteReplace string // 重写规则的目标 rewriteReplace string // 重写规则的目标
rewriteIsExternalURL bool // 重写目标是否为外部URL rewriteIsExternalURL bool // 重写目标是否为外部URL
cacheRef *serverconfigs.HTTPCacheRef // 缓存设置 remoteAddr string // 计算后的RemoteAddr
cacheKey string // 缓存使用的Key
isCached bool // 是否已经被缓存 cacheRef *serverconfigs.HTTPCacheRef // 缓存设置
isAttack bool // 是否是攻击请求 cacheKey string // 缓存使用的Key
bodyData []byte // 读取的Body内容 isCached bool // 是否已经被缓存
cacheCanTryStale bool // 是否可以尝试使用Stale缓存
isAttack bool // 是否是攻击请求
requestBodyData []byte // 读取的Body内容
// WAF相关 // WAF相关
firewallPolicyId int64 firewallPolicyId int64
@@ -81,30 +86,44 @@ type HTTPRequest struct {
logAttrs map[string]string logAttrs map[string]string
disableLog bool // 此请求中关闭Log disableLog bool // 此请求中关闭Log
// script相关操作
isDone bool
} }
// 初始化 // 初始化
func (this *HTTPRequest) init() { func (this *HTTPRequest) init() {
this.writer = NewHTTPWriter(this, this.RawWriter) this.writer = NewHTTPWriter(this, this.RawWriter)
this.web = &serverconfigs.HTTPWebConfig{IsOn: true} this.web = &serverconfigs.HTTPWebConfig{
IsOn: true,
}
// this.uri = this.RawReq.URL.RequestURI() // this.uri = this.RawReq.URL.RequestURI()
// 之所以不使用RequestURI()是不想让URL中的Path被Encode // 之所以不使用RequestURI()是不想让URL中的Path被Encode
var urlPath = this.RawReq.URL.Path
if this.ReqServer.Web != nil && this.ReqServer.Web.MergeSlashes {
urlPath = utils.CleanPath(urlPath)
this.web.MergeSlashes = true
}
if len(this.RawReq.URL.RawQuery) > 0 { if len(this.RawReq.URL.RawQuery) > 0 {
this.uri = this.RawReq.URL.Path + "?" + this.RawReq.URL.RawQuery this.uri = urlPath + "?" + this.RawReq.URL.RawQuery
} else { } else {
this.uri = this.RawReq.URL.Path this.uri = urlPath
} }
this.rawURI = this.uri this.rawURI = this.uri
this.varMapping = map[string]string{ this.varMapping = map[string]string{
// 缓存相关初始化 // 缓存相关初始化
"cache.status": "BYPASS", "cache.status": "BYPASS",
"cache.age": "0",
"cache.key": "",
"cache.policy.name": "", "cache.policy.name": "",
"cache.policy.id": "0", "cache.policy.id": "0",
"cache.policy.type": "", "cache.policy.type": "",
} }
this.logAttrs = map[string]string{} this.logAttrs = map[string]string{}
this.requestFromTime = time.Now() this.requestFromTime = time.Now()
this.requestId = httpRequestNextId()
} }
// Do 执行请求 // Do 执行请求
@@ -113,15 +132,22 @@ func (this *HTTPRequest) Do() {
this.init() this.init()
// 当前服务的反向代理配置 // 当前服务的反向代理配置
if this.Server.ReverseProxyRef != nil && this.Server.ReverseProxy != nil { if this.ReqServer.ReverseProxyRef != nil && this.ReqServer.ReverseProxy != nil {
this.reverseProxyRef = this.Server.ReverseProxyRef this.reverseProxyRef = this.ReqServer.ReverseProxyRef
this.reverseProxy = this.Server.ReverseProxy this.reverseProxy = this.ReqServer.ReverseProxy
} }
// Web配置 // Web配置
err := this.configureWeb(this.Server.Web, true, 0) err := this.configureWeb(this.ReqServer.Web, true, 0)
if err != nil { if err != nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
this.doEnd()
return
}
// 回调事件
this.onInit()
if this.writer.isFinished {
this.doEnd() this.doEnd()
return return
} }
@@ -138,14 +164,14 @@ func (this *HTTPRequest) Do() {
} }
// 套餐 // 套餐
if this.Server.UserPlan != nil && !this.Server.UserPlan.IsAvailable() { if this.ReqServer.UserPlan != nil && !this.ReqServer.UserPlan.IsAvailable() {
this.doPlanExpires() this.doPlanExpires()
this.doEnd() this.doEnd()
return return
} }
// 流量限制 // 流量限制
if this.Server.TrafficLimit != nil && this.Server.TrafficLimit.IsOn && !this.Server.TrafficLimit.IsEmpty() && this.Server.TrafficLimitStatus != nil && this.Server.TrafficLimitStatus.IsValid() { if this.ReqServer.TrafficLimit != nil && this.ReqServer.TrafficLimit.IsOn && !this.ReqServer.TrafficLimit.IsEmpty() && this.ReqServer.TrafficLimitStatus != nil && this.ReqServer.TrafficLimitStatus.IsValid() {
this.doTrafficLimit() this.doTrafficLimit()
this.doEnd() this.doEnd()
return return
@@ -192,6 +218,28 @@ func (this *HTTPRequest) Do() {
// 开始调用 // 开始调用
func (this *HTTPRequest) doBegin() { func (this *HTTPRequest) doBegin() {
// 处理request limit
if this.web.RequestLimit != nil &&
this.web.RequestLimit.IsOn {
if this.doRequestLimit() {
return
}
}
// 处理requestBody
if this.RawReq.ContentLength > 0 &&
this.web.AccessLogRef != nil &&
this.web.AccessLogRef.IsOn &&
this.web.AccessLogRef.ContainsField(serverconfigs.HTTPAccessLogFieldRequestBody) {
var err error
this.requestBodyData, err = ioutil.ReadAll(io.LimitReader(this.RawReq.Body, AccessLogMaxRequestBodySize))
if err != nil {
this.write50x(err, http.StatusBadGateway, false)
return
}
this.RawReq.Body = ioutil.NopCloser(io.MultiReader(bytes.NewBuffer(this.requestBodyData), this.RawReq.Body))
}
// 处理健康检查 // 处理健康检查
var healthCheckKey = this.RawReq.Header.Get(serverconfigs.HealthCheckHeaderName) var healthCheckKey = this.RawReq.Header.Get(serverconfigs.HealthCheckHeaderName)
if len(healthCheckKey) > 0 { if len(healthCheckKey) > 0 {
@@ -200,11 +248,6 @@ func (this *HTTPRequest) doBegin() {
} }
} }
// 统计
if this.web.StatRef != nil && this.web.StatRef.IsOn {
this.doStat()
}
// 跳转 // 跳转
if len(this.web.HostRedirects) > 0 { if len(this.web.HostRedirects) > 0 {
if this.doHostRedirect() { if this.doHostRedirect() {
@@ -220,7 +263,7 @@ func (this *HTTPRequest) doBegin() {
// 缓存 // 缓存
if this.web.Cache != nil && this.web.Cache.IsOn { if this.web.Cache != nil && this.web.Cache.IsOn {
if this.doCacheRead() { if this.doCacheRead(false) {
return return
} }
} }
@@ -270,14 +313,14 @@ func (this *HTTPRequest) doEnd() {
// 流量统计 // 流量统计
// TODO 增加是否开启开关 // TODO 增加是否开启开关
// TODO 增加Header统计考虑从Conn中读取 // TODO 增加Header统计考虑从Conn中读取
if this.Server != nil { if this.ReqServer != nil {
if this.isCached { if this.isCached {
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, this.writer.sentBodyBytes, 1, 1, 0, 0, this.Server.ShouldCheckTrafficLimit(), this.Server.PlanId()) stats.SharedTrafficStatManager.Add(this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes(), this.writer.SentBodyBytes(), 1, 1, 0, 0, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
} else { } else {
if this.isAttack { if this.isAttack {
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 1, this.writer.sentBodyBytes, this.Server.ShouldCheckTrafficLimit(), this.Server.PlanId()) stats.SharedTrafficStatManager.Add(this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes(), 0, 1, 0, 1, this.writer.SentBodyBytes(), this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
} else { } else {
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 0, 0, this.Server.ShouldCheckTrafficLimit(), this.Server.PlanId()) stats.SharedTrafficStatManager.Add(this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes(), 0, 1, 0, 0, 0, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
} }
} }
} }
@@ -286,6 +329,12 @@ func (this *HTTPRequest) doEnd() {
if metrics.SharedManager.HasHTTPMetrics() { if metrics.SharedManager.HasHTTPMetrics() {
this.doMetricsResponse() this.doMetricsResponse()
} }
// 统计
if this.web.StatRef != nil && this.web.StatRef.IsOn {
// 放到最后执行
this.doStat()
}
} }
// RawURI 原始的请求URI // RawURI 原始的请求URI
@@ -411,6 +460,31 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
this.web.Auth = web.Auth this.web.Auth = web.Auth
} }
// request limit
if web.RequestLimit != nil && (web.RequestLimit.IsPrior || isTop) {
this.web.RequestLimit = web.RequestLimit
}
// request scripts
if web.RequestScripts != nil {
if this.web.RequestScripts == nil {
this.web.RequestScripts = web.RequestScripts
} else {
if web.RequestScripts.InitGroup != nil && (web.RequestScripts.InitGroup.IsPrior || isTop) {
if this.web.RequestScripts == nil {
this.web.RequestScripts = &serverconfigs.HTTPRequestScriptsConfig{}
}
this.web.RequestScripts.InitGroup = web.RequestScripts.InitGroup
}
if web.RequestScripts.RequestGroup != nil && (web.RequestScripts.RequestGroup.IsPrior || isTop) {
if this.web.RequestScripts == nil {
this.web.RequestScripts = &serverconfigs.HTTPRequestScriptsConfig{}
}
this.web.RequestScripts.RequestGroup = web.RequestScripts.RequestGroup
}
}
}
// 重写规则 // 重写规则
if len(web.RewriteRefs) > 0 { if len(web.RewriteRefs) > 0 {
for index, ref := range web.RewriteRefs { for index, ref := range web.RewriteRefs {
@@ -479,6 +553,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
continue continue
} }
if varMapping, isMatched := location.Match(rawPath, this.Format); isMatched { if varMapping, isMatched := location.Match(rawPath, this.Format); isMatched {
// 检查专属域名
if len(location.Domains) > 0 && !configutils.MatchDomains(location.Domains, this.ReqHost) {
continue
}
if len(varMapping) > 0 { if len(varMapping) > 0 {
this.addVarMapping(varMapping) this.addVarMapping(varMapping)
} }
@@ -548,6 +627,8 @@ func (this *HTTPRequest) Format(source string) string {
return strconv.Itoa(this.requestRemotePort()) return strconv.Itoa(this.requestRemotePort())
case "remoteUser": case "remoteUser":
return this.requestRemoteUser() return this.requestRemoteUser()
case "requestId":
return this.requestId
case "requestURI", "requestUri": case "requestURI", "requestUri":
return this.rawURI return this.rawURI
case "requestURL": case "requestURL":
@@ -555,11 +636,11 @@ func (this *HTTPRequest) Format(source string) string {
if this.IsHTTPS { if this.IsHTTPS {
scheme = "https" scheme = "https"
} }
return scheme + "://" + this.Host + this.rawURI return scheme + "://" + this.ReqHost + this.rawURI
case "requestPath": case "requestPath":
return this.requestPath() return this.Path()
case "requestPathExtension": case "requestPathExtension":
return filepath.Ext(this.requestPath()) return filepath.Ext(this.Path())
case "requestLength": case "requestLength":
return strconv.FormatInt(this.requestLength(), 10) return strconv.FormatInt(this.requestLength(), 10)
case "requestTime": case "requestTime":
@@ -573,7 +654,7 @@ func (this *HTTPRequest) Format(source string) string {
} }
if this.web.Root != nil && this.web.Root.IsOn { if this.web.Root != nil && this.web.Root.IsOn {
return filepath.Clean(this.web.Root.Dir + this.requestPath()) return filepath.Clean(this.web.Root.Dir + this.Path())
} }
return "" return ""
@@ -602,7 +683,7 @@ func (this *HTTPRequest) Format(source string) string {
case "timestamp": case "timestamp":
return strconv.FormatInt(this.requestFromTime.Unix(), 10) return strconv.FormatInt(this.requestFromTime.Unix(), 10)
case "host": case "host":
return this.Host return this.ReqHost
case "referer": case "referer":
return this.RawReq.Referer() return this.RawReq.Referer()
case "referer.host": case "referer.host":
@@ -619,6 +700,11 @@ func (this *HTTPRequest) Format(source string) string {
return this.requestString() return this.requestString()
case "cookies": case "cookies":
return this.requestCookiesString() return this.requestCookiesString()
case "isArgs":
if strings.Contains(this.uri, "?") {
return "?"
}
return ""
case "args", "queryString": case "args", "queryString":
return this.requestQueryString() return this.requestQueryString()
case "headers": case "headers":
@@ -715,7 +801,7 @@ func (this *HTTPRequest) Format(source string) string {
// host // host
if prefix == "host" { if prefix == "host" {
pieces := strings.Split(this.Host, ".") pieces := strings.Split(this.ReqHost, ".")
switch suffix { switch suffix {
case "first": case "first":
if len(pieces) > 0 { if len(pieces) > 0 {
@@ -780,6 +866,104 @@ func (this *HTTPRequest) Format(source string) string {
} }
} }
// geo
if prefix == "geo" {
result, _ := iplibrary.SharedLibrary.Lookup(this.requestRemoteAddr(true))
switch suffix {
case "country.name":
if result != nil {
return result.Country
}
return ""
case "country.id":
if result != nil {
return types.String(iplibrary.SharedCountryManager.Lookup(result.Country))
}
return "0"
case "province.name":
if result != nil {
return result.Province
}
return ""
case "province.id":
if result != nil {
return types.String(iplibrary.SharedProvinceManager.Lookup(result.Province))
}
return "0"
case "city.name":
if result != nil {
return result.City
}
return ""
case "city.id":
if result != nil {
var provinceId = iplibrary.SharedProvinceManager.Lookup(result.Province)
if provinceId > 0 {
return types.String(iplibrary.SharedCityManager.Lookup(provinceId, result.City))
} else {
return "0"
}
}
return "0"
}
}
// ips
if prefix == "isp" {
result, _ := iplibrary.SharedLibrary.Lookup(this.requestRemoteAddr(true))
switch suffix {
case "name":
if result != nil {
return result.ISP
}
case "id":
if result != nil {
return types.String(iplibrary.SharedProviderManager.Lookup(result.ISP))
}
return "0"
}
return ""
}
// browser
if prefix == "browser" {
var result = stats.SharedUserAgentParser.Parse(this.RawReq.UserAgent())
switch suffix {
case "os.name":
return result.OS.Name
case "os.version":
return result.OS.Version
case "name":
return result.BrowserName
case "version":
return result.BrowserVersion
case "isMobile":
if result.IsMobile {
return "1"
} else {
return "0"
}
}
}
// product
if prefix == "product" {
switch suffix {
case "name":
if sharedNodeConfig.ProductConfig != nil && len(sharedNodeConfig.ProductConfig.Name) > 0 {
return sharedNodeConfig.ProductConfig.Name
}
return teaconst.GlobalProductName
case "version":
if sharedNodeConfig.ProductConfig != nil && len(sharedNodeConfig.ProductConfig.Version) > 0 {
return sharedNodeConfig.ProductConfig.Version
}
return teaconst.Version
}
}
return "${" + varName + "}" return "${" + varName + "}"
}) })
} }
@@ -793,12 +977,17 @@ func (this *HTTPRequest) addVarMapping(varMapping map[string]string) {
// 获取请求的客户端地址 // 获取请求的客户端地址
func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string { func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
if supportVar && len(this.remoteAddr) > 0 {
return this.remoteAddr
}
if supportVar && if supportVar &&
this.web.RemoteAddr != nil && this.web.RemoteAddr != nil &&
this.web.RemoteAddr.IsOn && this.web.RemoteAddr.IsOn &&
!this.web.RemoteAddr.IsEmpty() { !this.web.RemoteAddr.IsEmpty() {
var remoteAddr = this.Format(this.web.RemoteAddr.Value) var remoteAddr = this.Format(this.web.RemoteAddr.Value)
if net.ParseIP(remoteAddr) != nil { if net.ParseIP(remoteAddr) != nil {
this.remoteAddr = remoteAddr
return remoteAddr return remoteAddr
} }
} }
@@ -811,6 +1000,9 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
forwardedFor = forwardedFor[:commaIndex] forwardedFor = forwardedFor[:commaIndex]
} }
if net.ParseIP(forwardedFor) != nil { if net.ParseIP(forwardedFor) != nil {
if supportVar {
this.remoteAddr = forwardedFor
}
return forwardedFor return forwardedFor
} }
} }
@@ -820,6 +1012,9 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
realIP, ok := this.RawReq.Header["X-Real-IP"] realIP, ok := this.RawReq.Header["X-Real-IP"]
if ok && len(realIP) > 0 { if ok && len(realIP) > 0 {
if net.ParseIP(realIP[0]) != nil { if net.ParseIP(realIP[0]) != nil {
if supportVar {
this.remoteAddr = realIP[0]
}
return realIP[0] return realIP[0]
} }
} }
@@ -830,6 +1025,9 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
realIP, ok := this.RawReq.Header["X-Real-Ip"] realIP, ok := this.RawReq.Header["X-Real-Ip"]
if ok && len(realIP) > 0 { if ok && len(realIP) > 0 {
if net.ParseIP(realIP[0]) != nil { if net.ParseIP(realIP[0]) != nil {
if supportVar {
this.remoteAddr = realIP[0]
}
return realIP[0] return realIP[0]
} }
} }
@@ -839,6 +1037,9 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
remoteAddr := this.RawReq.RemoteAddr remoteAddr := this.RawReq.RemoteAddr
host, _, err := net.SplitHostPort(remoteAddr) host, _, err := net.SplitHostPort(remoteAddr)
if err == nil { if err == nil {
if supportVar {
this.remoteAddr = host
}
return host return host
} else { } else {
return remoteAddr return remoteAddr
@@ -897,8 +1098,8 @@ func (this *HTTPRequest) requestRemoteUser() string {
return username return username
} }
// 请求的URL中路径部分 // Path 请求的URL中路径部分
func (this *HTTPRequest) requestPath() string { func (this *HTTPRequest) Path() string {
uri, err := url.ParseRequestURI(this.rawURI) uri, err := url.ParseRequestURI(this.rawURI)
if err != nil { if err != nil {
return "" return ""
@@ -915,7 +1116,7 @@ func (this *HTTPRequest) requestRemotePort() int {
return 0 return 0
} }
// 情趣的URI中的参数部分 // 获取的URI中的参数部分
func (this *HTTPRequest) requestQueryString() string { func (this *HTTPRequest) requestQueryString() string {
uri, err := url.ParseRequestURI(this.uri) uri, err := url.ParseRequestURI(this.uri)
if err != nil { if err != nil {
@@ -1008,9 +1209,148 @@ func (this *HTTPRequest) requestServerPort() int {
return 0 return 0
} }
// 获取完整的URL func (this *HTTPRequest) Id() string {
func (this *HTTPRequest) requestFullURL() string { return this.requestId
return this.requestScheme() + "://" + this.Host + this.uri }
func (this *HTTPRequest) Server() maps.Map {
return maps.Map{"id": this.ReqServer.Id}
}
func (this *HTTPRequest) Node() maps.Map {
return maps.Map{"id": teaconst.NodeId}
}
// URL 获取完整的URL
func (this *HTTPRequest) URL() string {
return this.requestScheme() + "://" + this.ReqHost + this.uri
}
// Host 获取Host
func (this *HTTPRequest) Host() string {
return this.ReqHost
}
func (this *HTTPRequest) Proto() string {
return this.RawReq.Proto
}
func (this *HTTPRequest) ProtoMajor() int {
return this.RawReq.ProtoMajor
}
func (this *HTTPRequest) ProtoMinor() int {
return this.RawReq.ProtoMinor
}
func (this *HTTPRequest) RemoteAddr() string {
return this.requestRemoteAddr(true)
}
func (this *HTTPRequest) RawRemoteAddr() string {
addr := this.RawReq.RemoteAddr
host, _, err := net.SplitHostPort(addr)
if err == nil {
addr = host
}
return addr
}
func (this *HTTPRequest) RemotePort() int {
addr := this.RawReq.RemoteAddr
_, port, err := net.SplitHostPort(addr)
if err != nil {
return 0
}
return types.Int(port)
}
func (this *HTTPRequest) SetAttr(name string, value string) {
this.logAttrs[name] = value
}
func (this *HTTPRequest) SetVar(name string, value string) {
this.varMapping[name] = value
}
// ContentLength 请求内容长度
func (this *HTTPRequest) ContentLength() int64 {
return this.RawReq.ContentLength
}
// Method 请求方法
func (this *HTTPRequest) Method() string {
return this.RawReq.Method
}
// TransferEncoding 获取传输编码
func (this *HTTPRequest) TransferEncoding() string {
if len(this.RawReq.TransferEncoding) > 0 {
return this.RawReq.TransferEncoding[0]
}
return ""
}
// Cookie 获取Cookie
func (this *HTTPRequest) Cookie(name string) string {
c, err := this.RawReq.Cookie(name)
if err != nil {
return ""
}
return c.Value
}
// DeleteHeader 删除Header
func (this *HTTPRequest) DeleteHeader(name string) {
this.RawReq.Header.Del(name)
}
// SetHeader 设置Header
func (this *HTTPRequest) SetHeader(name string, values []string) {
this.RawReq.Header[name] = values
}
// Header 读取Header
func (this *HTTPRequest) Header() http.Header {
return this.RawReq.Header
}
// URI 获取当前请求的URI
func (this *HTTPRequest) URI() string {
return this.uri
}
// SetURI 设置当前请求的URI
func (this *HTTPRequest) SetURI(uri string) {
this.uri = uri
}
// Done 设置已完成
func (this *HTTPRequest) Done() {
this.isDone = true
}
// Close 关闭连接
func (this *HTTPRequest) Close() {
this.Done()
requestConn := this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return
}
conn, ok := requestConn.(net.Conn)
if ok {
_ = conn.Close()
return
}
return
}
// Allow 放行
func (this *HTTPRequest) Allow() {
this.web.FirewallRef = nil
} }
// 设置代理相关头部信息 // 设置代理相关头部信息
@@ -1053,9 +1393,9 @@ func (this *HTTPRequest) setForwardHeaders(header http.Header) {
/**{ /**{
forwarded, ok := header["Forwarded"] forwarded, ok := header["Forwarded"]
if ok { if ok {
header["Forwarded"] = []string{strings.Join(forwarded, ", ") + ", by=" + this.serverAddr + "; for=" + remoteAddr + "; host=" + this.host + "; proto=" + this.rawScheme} header["Forwarded"] = []string{strings.Join(forwarded, ", ") + ", by=" + this.serverAddr + "; for=" + remoteAddr + "; host=" + this.ReqHost + "; proto=" + this.rawScheme}
} else { } else {
header["Forwarded"] = []string{"by=" + this.serverAddr + "; for=" + remoteAddr + "; host=" + this.host + "; proto=" + this.rawScheme} header["Forwarded"] = []string{"by=" + this.serverAddr + "; for=" + remoteAddr + "; host=" + this.ReqHost + "; proto=" + this.rawScheme}
} }
}**/ }**/
@@ -1066,7 +1406,7 @@ func (this *HTTPRequest) setForwardHeaders(header http.Header) {
if this.reverseProxy != nil && this.reverseProxy.ShouldAddXForwardedHostHeader() { if this.reverseProxy != nil && this.reverseProxy.ShouldAddXForwardedHostHeader() {
if _, ok := header["X-Forwarded-Host"]; !ok { if _, ok := header["X-Forwarded-Host"]; !ok {
this.RawReq.Header.Set("X-Forwarded-Host", this.Host) this.RawReq.Header.Set("X-Forwarded-Host", this.ReqHost)
} }
} }
@@ -1089,44 +1429,53 @@ func (this *HTTPRequest) processRequestHeaders(reqHeader http.Header) {
} }
} }
// Add
for _, header := range this.web.RequestHeaderPolicy.AddHeaders {
if !header.IsOn {
continue
}
oldValues, _ := this.RawReq.Header[header.Name]
newHeaderValue := header.Value // 因为我们不能修改header所以在这里使用新变量
if header.HasVariables() {
newHeaderValue = this.Format(header.Value)
}
oldValues = append(oldValues, newHeaderValue)
reqHeader[header.Name] = oldValues
// 支持修改Host
if header.Name == "Host" && len(header.Value) > 0 {
this.RawReq.Host = newHeaderValue
}
}
// Set // Set
for _, header := range this.web.RequestHeaderPolicy.SetHeaders { for _, header := range this.web.RequestHeaderPolicy.SetHeaders {
if !header.IsOn { if !header.IsOn {
continue continue
} }
newHeaderValue := header.Value // 因为我们不能修改header所以在这里使用新变量
if header.HasVariables() { // 是否已删除
newHeaderValue = this.Format(header.Value) if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) {
continue
}
// 请求方法
if len(header.Methods) > 0 && !lists.ContainsString(header.Methods, this.RawReq.Method) {
continue
}
// 域名
if len(header.Domains) > 0 && !configutils.MatchDomains(header.Domains, this.ReqHost) {
continue
}
var headerValue = header.Value
if header.ShouldReplace {
if len(headerValue) == 0 {
headerValue = reqHeader.Get(header.Name) // 原有值
} else if header.HasVariables() {
headerValue = this.Format(header.Value)
}
for _, v := range header.ReplaceValues {
headerValue = v.Replace(headerValue)
}
} else if header.HasVariables() {
headerValue = this.Format(header.Value)
} }
reqHeader[header.Name] = []string{newHeaderValue}
// 支持修改Host // 支持修改Host
if header.Name == "Host" && len(header.Value) > 0 { if header.Name == "Host" && len(header.Value) > 0 {
this.RawReq.Host = newHeaderValue this.RawReq.Host = headerValue
} else {
if header.ShouldAppend {
reqHeader[header.Name] = append(reqHeader[header.Name], headerValue)
} else {
reqHeader[header.Name] = []string{headerValue}
}
} }
} }
// Replace
// TODO 需要实现
} }
} }
@@ -1151,7 +1500,6 @@ func (this *HTTPRequest) processResponseHeaders(statusCode int) {
// 删除/添加/替换Header // 删除/添加/替换Header
// TODO 实现AddTrailers // TODO 实现AddTrailers
// TODO 实现ReplaceHeaders
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn { if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {
// 删除某些Header // 删除某些Header
for name := range responseHeader { for name := range responseHeader {
@@ -1160,55 +1508,69 @@ func (this *HTTPRequest) processResponseHeaders(statusCode int) {
} }
} }
// Add
for _, header := range this.web.ResponseHeaderPolicy.AddHeaders {
if !header.IsOn {
continue
}
if header.Match(statusCode) {
if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) {
continue
}
oldValues, _ := responseHeader[header.Name]
if header.HasVariables() {
oldValues = append(oldValues, this.Format(header.Value))
} else {
oldValues = append(oldValues, header.Value)
}
responseHeader[header.Name] = oldValues
}
}
// Set // Set
for _, header := range this.web.ResponseHeaderPolicy.SetHeaders { for _, header := range this.web.ResponseHeaderPolicy.SetHeaders {
if !header.IsOn { if !header.IsOn {
continue continue
} }
if header.Match(statusCode) {
if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) { // 是否已删除
continue if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) {
continue
}
// 状态码
if header.Status != nil && !header.Status.Match(statusCode) {
continue
}
// 请求方法
if len(header.Methods) > 0 && !lists.ContainsString(header.Methods, this.RawReq.Method) {
continue
}
// 域名
if len(header.Domains) > 0 && !configutils.MatchDomains(header.Domains, this.ReqHost) {
continue
}
// 是否为跳转
if header.DisableRedirect && httpStatusIsRedirect(statusCode) {
continue
}
var headerValue = header.Value
if header.ShouldReplace {
if len(headerValue) == 0 {
headerValue = responseHeader.Get(header.Name) // 原有值
} else if header.HasVariables() {
headerValue = this.Format(header.Value)
} }
if header.HasVariables() {
responseHeader[header.Name] = []string{this.Format(header.Value)} for _, v := range header.ReplaceValues {
} else { headerValue = v.Replace(headerValue)
responseHeader[header.Name] = []string{header.Value}
} }
} else if header.HasVariables() {
headerValue = this.Format(header.Value)
}
if header.ShouldAppend {
responseHeader[header.Name] = append(responseHeader[header.Name], headerValue)
} else {
responseHeader[header.Name] = []string{headerValue}
} }
} }
// Replace
// TODO
} }
// HSTS // HSTS
if this.IsHTTPS && if this.IsHTTPS &&
this.Server.HTTPS != nil && this.ReqServer.HTTPS != nil &&
this.Server.HTTPS.SSLPolicy != nil && this.ReqServer.HTTPS.SSLPolicy != nil &&
this.Server.HTTPS.SSLPolicy.IsOn && this.ReqServer.HTTPS.SSLPolicy.IsOn &&
this.Server.HTTPS.SSLPolicy.HSTS != nil && this.ReqServer.HTTPS.SSLPolicy.HSTS != nil &&
this.Server.HTTPS.SSLPolicy.HSTS.IsOn && this.ReqServer.HTTPS.SSLPolicy.HSTS.IsOn &&
this.Server.HTTPS.SSLPolicy.HSTS.Match(this.Host) { this.ReqServer.HTTPS.SSLPolicy.HSTS.Match(this.ReqHost) {
responseHeader.Set(this.Server.HTTPS.SSLPolicy.HSTS.HeaderKey(), this.Server.HTTPS.SSLPolicy.HSTS.HeaderValue()) responseHeader.Set(this.ReqServer.HTTPS.SSLPolicy.HSTS.HeaderKey(), this.ReqServer.HTTPS.SSLPolicy.HSTS.HeaderValue())
} }
} }
@@ -1222,19 +1584,16 @@ func (this *HTTPRequest) addError(err error) {
// 计算合适的buffer size // 计算合适的buffer size
func (this *HTTPRequest) bytePool(contentLength int64) *utils.BytePool { func (this *HTTPRequest) bytePool(contentLength int64) *utils.BytePool {
if contentLength <= 0 { if contentLength < 8192 { // 8K
return bytePool1k return utils.BytePool1k
}
if contentLength < 1024 { // 1K
return bytePool256b
} }
if contentLength < 32768 { // 32K if contentLength < 32768 { // 32K
return bytePool1k return utils.BytePool4k
} }
if contentLength < 1048576 { // 1M if contentLength < 131072 { // 128K
return bytePool32k return utils.BytePool16k
} }
return bytePool128k return utils.BytePool32k
} }
// 检查是否可以忽略错误 // 检查是否可以忽略错误
@@ -1271,3 +1630,18 @@ func (this *HTTPRequest) canIgnore(err error) bool {
return false return false
} }
// 检查连接是否已关闭
func (this *HTTPRequest) isConnClosed() bool {
requestConn := this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return true
}
conn, ok := requestConn.(net.Conn)
if ok {
return isClientConnClosed(conn)
}
return true
}

View File

@@ -27,13 +27,13 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
subReq.ProtoMinor = this.RawReq.ProtoMinor subReq.ProtoMinor = this.RawReq.ProtoMinor
subReq.ProtoMajor = this.RawReq.ProtoMajor subReq.ProtoMajor = this.RawReq.ProtoMajor
subReq.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) subReq.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
subReq.Header.Set("Referer", this.requestFullURL()) subReq.Header.Set("Referer", this.URL())
var writer = NewEmptyResponseWriter(this.writer) var writer = NewEmptyResponseWriter(this.writer)
this.doSubRequest(writer, subReq) this.doSubRequest(writer, subReq)
return writer.StatusCode(), nil return writer.StatusCode(), nil
}, this.Format) }, this.Format)
if err != nil { if err != nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
return return
} }
if b { if b {
@@ -45,7 +45,7 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
if len(method.Realm) > 0 { if len(method.Realm) > 0 {
headerValue += method.Realm headerValue += method.Realm
} else { } else {
headerValue += this.Host headerValue += this.ReqHost
} }
headerValue += "\"" headerValue += "\""
if len(method.Charset) > 0 { if len(method.Charset) > 0 {

View File

@@ -5,8 +5,11 @@ import (
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -15,8 +18,10 @@ import (
) )
// 读取缓存 // 读取缓存
func (this *HTTPRequest) doCacheRead() (shouldStop bool) { func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
cachePolicy := this.Server.HTTPCachePolicy this.cacheCanTryStale = false
var cachePolicy = this.ReqServer.HTTPCachePolicy
if cachePolicy == nil || !cachePolicy.IsOn { if cachePolicy == nil || !cachePolicy.IsOn {
return return
} }
@@ -80,6 +85,12 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
} }
} }
// 校验请求
if !this.cacheRef.MatchRequest(this.RawReq) {
this.cacheRef = nil
return
}
// 相关变量 // 相关变量
this.varMapping["cache.policy.name"] = cachePolicy.Name this.varMapping["cache.policy.name"] = cachePolicy.Name
this.varMapping["cache.policy.id"] = strconv.FormatInt(cachePolicy.Id, 10) this.varMapping["cache.policy.id"] = strconv.FormatInt(cachePolicy.Id, 10)
@@ -103,6 +114,7 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
} }
this.cacheKey = key this.cacheKey = key
this.varMapping["cache.key"] = key
// 读取缓存 // 读取缓存
storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id) storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
@@ -113,17 +125,19 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
// 判断是否在Purge // 判断是否在Purge
if this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey { if this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey {
this.varMapping["cache.status"] = "PURGE"
err := storage.Delete(key) err := storage.Delete(key)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error()) remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
} }
go func() { goman.New(func() {
rpcClient, err := rpc.SharedRPC() rpcClient, err := rpc.SharedRPC()
if err == nil { if err == nil {
for _, rpcServerService := range rpcClient.ServerRPCList() { for _, rpcServerService := range rpcClient.ServerRPCList() {
_, err = rpcServerService.PurgeServerCache(rpcClient.Context(), &pb.PurgeServerCacheRequest{ _, err = rpcServerService.PurgeServerCache(rpcClient.Context(), &pb.PurgeServerCacheRequest{
Domains: []string{this.Host}, Domains: []string{this.ReqHost},
Keys: []string{key}, Keys: []string{key},
Prefixes: nil, Prefixes: nil,
}) })
@@ -132,52 +146,76 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
} }
} }
} }
}() })
return true return true
} }
buf := bytePool32k.Get() // 调用回调
defer func() { this.onRequest()
bytePool32k.Put(buf) if this.writer.isFinished {
}() return
}
var reader caches.Reader var reader caches.Reader
var err error var err error
// 是否优先检查WebP // 是否优先检查WebP
var isWebP = false
if this.web.WebP != nil && if this.web.WebP != nil &&
this.web.WebP.IsOn && this.web.WebP.IsOn &&
this.web.WebP.MatchRequest(filepath.Ext(this.requestPath()), this.Format) && this.web.WebP.MatchRequest(filepath.Ext(this.Path()), this.Format) &&
this.web.WebP.MatchAccept(this.requestHeader("Accept")) { this.web.WebP.MatchAccept(this.requestHeader("Accept")) {
reader, _ = storage.OpenReader(key + webpSuffix) reader, _ = storage.OpenReader(key+webpSuffix, useStale)
if reader != nil {
isWebP = true
}
} }
// 检查正常的文件 // 检查正常的文件
if reader == nil { if reader == nil {
reader, err = storage.OpenReader(key) reader, err = storage.OpenReader(key, useStale)
if err != nil { if err != nil {
if err == caches.ErrNotFound { if err == caches.ErrNotFound {
// cache相关变量 // cache相关变量
this.varMapping["cache.status"] = "MISS" this.varMapping["cache.status"] = "MISS"
if !useStale && this.web.Cache.Stale != nil && this.web.Cache.Stale.IsOn {
this.cacheCanTryStale = true
}
return return
} }
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", "read from cache failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: open cache failed: "+err.Error())
} }
return return
} }
} }
defer func() { defer func() {
_ = reader.Close() if !this.writer.DelayRead() {
_ = reader.Close()
}
}() }()
this.varMapping["cache.status"] = "HIT" if useStale {
this.logAttrs["cache.status"] = "HIT" this.varMapping["cache.status"] = "STALE"
this.logAttrs["cache.status"] = "STALE"
} else {
this.varMapping["cache.status"] = "HIT"
this.logAttrs["cache.status"] = "HIT"
}
// 准备Buffer
var pool = this.bytePool(reader.BodySize())
var buf = pool.Get()
defer func() {
pool.Put(buf)
}()
// 读取Header // 读取Header
headerBuf := []byte{} var headerBuf = []byte{}
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) { err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) {
headerBuf = append(headerBuf, buf[:n]...) headerBuf = append(headerBuf, buf[:n]...)
for { for {
@@ -199,13 +237,26 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
}) })
if err != nil { if err != nil {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", "read from cache failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read header failed: "+err.Error())
} }
return return
} }
// 设置cache.age变量
var age = strconv.FormatInt(reader.ExpiresAt()-utils.UnixTime(), 10)
this.varMapping["cache.age"] = age
if addStatusHeader { if addStatusHeader {
this.writer.Header().Set("X-Cache", "HIT, "+refType+", "+reader.TypeName()) if useStale {
this.writer.Header().Set("X-Cache", "STALE, "+refType+", "+reader.TypeName())
} else {
this.writer.Header().Set("X-Cache", "HIT, "+refType+", "+reader.TypeName())
}
} else {
this.writer.Header().Del("X-Cache")
}
if this.web.Cache.AddAgeHeader {
this.writer.Header().Set("Age", age)
} }
// ETag // ETag
@@ -214,7 +265,11 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
var eTag = "" var eTag = ""
var lastModifiedAt = reader.LastModified() var lastModifiedAt = reader.LastModified()
if lastModifiedAt > 0 { if lastModifiedAt > 0 {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\"" if isWebP {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "_webp" + "\""
} else {
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\""
}
respHeader.Del("Etag") respHeader.Del("Etag")
respHeader["ETag"] = []string{eTag} respHeader["ETag"] = []string{eTag}
} }
@@ -223,7 +278,7 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
// 这里强制设置Last-Modified如果先前源站设置了Last-Modified将会被覆盖避免因为源站的Last-Modified导致源站返回304 Not Modified // 这里强制设置Last-Modified如果先前源站设置了Last-Modified将会被覆盖避免因为源站的Last-Modified导致源站返回304 Not Modified
var modifiedTime = "" var modifiedTime = ""
if lastModifiedAt > 0 { if lastModifiedAt > 0 {
modifiedTime = time.Unix(lastModifiedAt, 0).Format("Mon, 02 Jan 2006 15:04:05 GMT") modifiedTime = time.Unix(utils.GMTUnixTime(lastModifiedAt), 0).Format("Mon, 02 Jan 2006 15:04:05") + " GMT"
respHeader.Set("Last-Modified", modifiedTime) respHeader.Set("Last-Modified", modifiedTime)
} }
@@ -250,6 +305,7 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
} }
this.processResponseHeaders(reader.Status()) this.processResponseHeaders(reader.Status())
this.addExpiresHeader(reader.ExpiresAt())
// 输出Body // 输出Body
if this.RawReq.Method == http.MethodHead { if this.RawReq.Method == http.MethodHead {
@@ -313,7 +369,6 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
} }
} }
respHeader := this.writer.Header()
if len(rangeSet) == 1 { if len(rangeSet) == 1 {
respHeader.Set("Content-Range", "bytes "+strconv.FormatInt(rangeSet[0][0], 10)+"-"+strconv.FormatInt(rangeSet[0][1], 10)+"/"+strconv.FormatInt(reader.BodySize(), 10)) respHeader.Set("Content-Range", "bytes "+strconv.FormatInt(rangeSet[0][0], 10)+"-"+strconv.FormatInt(rangeSet[0][1], 10)+"/"+strconv.FormatInt(reader.BodySize(), 10))
respHeader.Set("Content-Length", strconv.FormatInt(rangeSet[0][1]-rangeSet[0][0]+1, 10)) respHeader.Set("Content-Length", strconv.FormatInt(rangeSet[0][1]-rangeSet[0][0]+1, 10))
@@ -327,13 +382,15 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
return true, nil return true, nil
}) })
if err != nil { if err != nil {
this.varMapping["cache.status"] = "MISS"
if err == caches.ErrInvalidRange { if err == caches.ErrInvalidRange {
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return true return true
} }
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", "read from cache failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
} }
return return
} }
@@ -379,7 +436,7 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
}) })
if err != nil { if err != nil {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", "read from cache failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
} }
return true return true
} }
@@ -387,23 +444,25 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
_, err = this.writer.WriteString("\r\n--" + boundary + "--\r\n") _, err = this.writer.WriteString("\r\n--" + boundary + "--\r\n")
if err != nil { if err != nil {
this.varMapping["cache.status"] = "MISS"
// 不提示写入客户端错误 // 不提示写入客户端错误
return true return true
} }
} else { // 没有Range } else { // 没有Range
this.writer.PrepareCompression(reader.BodySize()) var resp = &http.Response{Body: reader}
this.writer.Prepare(resp, reader.BodySize(), reader.Status(), false)
this.writer.WriteHeader(reader.Status()) this.writer.WriteHeader(reader.Status())
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) { _, err = io.CopyBuffer(this.writer, resp.Body, buf)
_, err = this.writer.Write(buf[:n]) if err == io.EOF {
if err != nil { err = nil
return false, errWritingToClient }
}
return true, nil
})
if err != nil { if err != nil {
this.varMapping["cache.status"] = "MISS"
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_CACHE", "read from cache failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read body failed: "+err.Error())
} }
return return
} }
@@ -417,3 +476,19 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
return true return true
} }
// 设置Expires Header
func (this *HTTPRequest) addExpiresHeader(expiresAt int64) {
if this.cacheRef.ExpiresTime != nil && this.cacheRef.ExpiresTime.IsPrior && this.cacheRef.ExpiresTime.IsOn {
if this.cacheRef.ExpiresTime.Overwrite || len(this.writer.Header().Get("Expires")) == 0 {
if this.cacheRef.ExpiresTime.AutoCalculate {
this.writer.Header().Set("Expires", time.Unix(utils.GMTUnixTime(expiresAt), 0).Format("Mon, 2 Jan 2006 15:04:05")+" GMT")
} else if this.cacheRef.ExpiresTime.Duration != nil {
var duration = this.cacheRef.ExpiresTime.Duration.Duration()
if duration > 0 {
this.writer.Header().Set("Expires", utils.GMTTime(time.Now().Add(duration)).Format("Mon, 2 Jan 2006 15:04:05")+" GMT")
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
package nodes package nodes
import ( import (
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net/http" "net/http"
) )
@@ -11,22 +12,42 @@ func (this *HTTPRequest) write404() {
} }
this.processResponseHeaders(http.StatusNotFound) this.processResponseHeaders(http.StatusNotFound)
msg := "404 page not found: '" + this.RawURI() + "'"
this.writer.WriteHeader(http.StatusNotFound) this.writer.WriteHeader(http.StatusNotFound)
_, _ = this.writer.Write([]byte(msg)) _, _ = this.writer.Write([]byte("404 page not found: '" + this.URL() + "'" + " (Request Id: " + this.requestId + ")"))
} }
func (this *HTTPRequest) write50x(err error, statusCode int) { func (this *HTTPRequest) writeCode(code int) {
if this.doPage(code) {
return
}
this.processResponseHeaders(code)
this.writer.WriteHeader(code)
_, _ = this.writer.Write([]byte(types.String(code) + " " + http.StatusText(code) + ": '" + this.URL() + "'" + " (Request Id: " + this.requestId + ")"))
}
func (this *HTTPRequest) write50x(err error, statusCode int, canTryStale bool) {
if err != nil { if err != nil {
this.addError(err) this.addError(err)
} }
// 尝试从缓存中恢复
if canTryStale &&
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, statusCode)) {
ok := this.doCacheRead(true)
if ok {
return
}
}
// 显示自定义页面
if this.doPage(statusCode) { if this.doPage(statusCode) {
return return
} }
this.processResponseHeaders(statusCode) this.processResponseHeaders(statusCode)
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(types.String(statusCode) + " " + http.StatusText(statusCode))) _, _ = this.writer.Write([]byte(types.String(statusCode) + " " + http.StatusText(statusCode) + " (Request Id: " + this.requestId + ")"))
} }

View File

@@ -0,0 +1,11 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !script
// +build !script
package nodes
func (this *HTTPRequest) onInit() {
}
func (this *HTTPRequest) onRequest() {
}

View File

@@ -52,13 +52,13 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
} }
} }
if !env.Has("SERVER_NAME") { if !env.Has("SERVER_NAME") {
env["SERVER_NAME"] = this.Host env["SERVER_NAME"] = this.ReqHost
} }
if !env.Has("REQUEST_URI") { if !env.Has("REQUEST_URI") {
env["REQUEST_URI"] = this.uri env["REQUEST_URI"] = this.uri
} }
if !env.Has("HOST") { if !env.Has("HOST") {
env["HOST"] = this.Host env["HOST"] = this.ReqHost
} }
if len(this.ServerAddr) > 0 { if len(this.ServerAddr) > 0 {
@@ -81,7 +81,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
client, err := fcgi.SharedPool(fastcgi.Network(), fastcgi.RealAddress(), uint(poolSize)).Client() client, err := fcgi.SharedPool(fastcgi.Network(), fastcgi.RealAddress(), uint(poolSize)).Client()
if err != nil { if err != nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
return return
} }
@@ -149,7 +149,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
host, found := params["HTTP_HOST"] host, found := params["HTTP_HOST"]
if !found || len(host) == 0 { if !found || len(host) == 0 {
params["HTTP_HOST"] = this.Host params["HTTP_HOST"] = this.ReqHost
} }
fcgiReq := fcgi.NewRequest() fcgiReq := fcgi.NewRequest()
@@ -159,13 +159,13 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
resp, stderr, err := client.Call(fcgiReq) resp, stderr, err := client.Call(fcgiReq)
if err != nil { if err != nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
return return
} }
if len(stderr) > 0 { if len(stderr) > 0 {
err := errors.New("Fastcgi Error: " + strings.TrimSpace(string(stderr)) + " script: " + maps.NewMap(params).GetString("SCRIPT_FILENAME")) err := errors.New("Fastcgi Error: " + strings.TrimSpace(string(stderr)) + " script: " + maps.NewMap(params).GetString("SCRIPT_FILENAME"))
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
return return
} }
@@ -190,7 +190,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
this.processResponseHeaders(resp.StatusCode) this.processResponseHeaders(resp.StatusCode)
// 准备 // 准备
this.writer.Prepare(resp.ContentLength, resp.StatusCode) this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)
// 设置响应代码 // 设置响应代码
this.writer.WriteHeader(resp.StatusCode) this.writer.WriteHeader(resp.StatusCode)

View File

@@ -14,7 +14,7 @@ func (this *HTTPRequest) doHealthCheck(key string) (stop bool) {
this.RawReq.Header.Del(serverconfigs.HealthCheckHeaderName) this.RawReq.Header.Del(serverconfigs.HealthCheckHeaderName)
data, err := nodeutils.DecryptData(sharedNodeConfig.NodeId, sharedNodeConfig.Secret, key) data, err := nodeutils.Base64DecodeMap(key)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_HEALTH_CHECK", "decode key failed: "+err.Error()) remotelogs.Error("HTTP_REQUEST_HEALTH_CHECK", "decode key failed: "+err.Error())
return return

View File

@@ -1,6 +1,7 @@
package nodes package nodes
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -8,7 +9,11 @@ import (
// 主机地址快速跳转 // 主机地址快速跳转
func (this *HTTPRequest) doHostRedirect() (blocked bool) { func (this *HTTPRequest) doHostRedirect() (blocked bool) {
fullURL := this.requestScheme() + "://" + this.Host + this.RawReq.URL.Path var urlPath = this.RawReq.URL.Path
if this.web.MergeSlashes {
urlPath = utils.CleanPath(urlPath)
}
fullURL := this.requestScheme() + "://" + this.ReqHost + urlPath
for _, u := range this.web.HostRedirects { for _, u := range this.web.HostRedirects {
if !u.IsOn { if !u.IsOn {
continue continue
@@ -22,9 +27,17 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
if u.KeepRequestURI { if u.KeepRequestURI {
afterURL += this.RawReq.URL.RequestURI() afterURL += this.RawReq.URL.RequestURI()
} }
// 前后是否一致
if fullURL == afterURL {
return false
}
if u.Status <= 0 { if u.Status <= 0 {
this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect) http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
} else { } else {
this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status) http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
} }
return true return true
@@ -55,18 +68,47 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
} }
} }
// 前后是否一致
if fullURL == afterURL {
return false
}
if u.KeepArgs {
var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 {
afterURL += this.uri[qIndex:]
}
}
if u.Status <= 0 { if u.Status <= 0 {
this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect) http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
} else { } else {
this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status) http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
} }
return true return true
} else { // 精准匹配 } else { // 精准匹配
if fullURL == u.RealBeforeURL() { if fullURL == u.RealBeforeURL() {
// 前后是否一致
if fullURL == u.AfterURL {
return false
}
var afterURL = u.AfterURL
if u.KeepArgs {
var qIndex = strings.Index(this.uri, "?")
if qIndex >= 0 {
afterURL += this.uri[qIndex:]
}
}
if u.Status <= 0 { if u.Status <= 0 {
http.Redirect(this.RawWriter, this.RawReq, u.AfterURL, http.StatusTemporaryRedirect) this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
} else { } else {
http.Redirect(this.RawWriter, this.RawReq, u.AfterURL, u.Status) this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
} }
return true return true
} }

View File

@@ -0,0 +1,32 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import "net/http"
func (this *HTTPRequest) doRequestLimit() (shouldStop bool) {
// 检查请求Body尺寸
// TODO 处理分片提交的内容
if this.web.RequestLimit.MaxBodyBytes() > 0 &&
this.RawReq.ContentLength > this.web.RequestLimit.MaxBodyBytes() {
this.writeCode(http.StatusRequestEntityTooLarge)
return true
}
// 设置连接相关参数
if this.web.RequestLimit.MaxConns > 0 || this.web.RequestLimit.MaxConnsPerIP > 0 {
requestConn := this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn != nil {
clientConn, ok := requestConn.(ClientConnInterface)
if ok && !clientConn.IsBound() {
if !clientConn.Bind(this.ReqServer.Id, this.requestRemoteAddr(true), this.web.RequestLimit.MaxConns, this.web.RequestLimit.MaxConnsPerIP) {
this.writeCode(http.StatusTooManyRequests)
this.Close()
return true
}
}
}
}
return false
}

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