Compare commits

...

69 Commits

Author SHA1 Message Date
刘祥超
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
126 changed files with 3152 additions and 809 deletions

View File

@@ -5,6 +5,7 @@ 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"
OS=${1} OS=${1}
ARCH=${2} ARCH=${2}
TAG=${3} TAG=${3}
@@ -53,7 +54,6 @@ function build() {
echo "building ..." echo "building ..."
MUSL_DIR="/usr/local/opt/musl-cross/bin"
CC_PATH="" CC_PATH=""
CXX_PATH="" CXX_PATH=""
if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then

View File

@@ -1,6 +1,7 @@
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"
@@ -88,6 +89,34 @@ 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.Run(func() { app.Run(func() {
node := nodes.NewNode() node := nodes.NewNode()
node.Start() node.Start()

23
go.mod
View File

@@ -5,7 +5,6 @@ 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
@@ -13,28 +12,24 @@ require (
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/dop251/goja v0.0.0-20210804101310-32956a348b49
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/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/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 golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d
golang.org/x/text v0.3.6 golang.org/x/text v0.3.7
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect golang.org/x/tools v0.1.3 // indirect
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/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
) )

44
go.sum
View File

@@ -1,4 +1,5 @@
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=
@@ -9,6 +10,7 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUW
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 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 +24,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=
@@ -37,10 +44,14 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA
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.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
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.20210217033140-668b12f5399d/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/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.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
@@ -63,6 +74,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,6 +87,7 @@ 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 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=
@@ -114,6 +127,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo= 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/mssola/user_agent v0.5.2/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
github.com/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578=
github.com/mssola/user_agent v0.5.3/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,8 +144,11 @@ 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/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc= github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 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=
@@ -138,20 +156,25 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
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 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/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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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 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=
@@ -163,19 +186,25 @@ 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-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I=
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=
@@ -199,6 +228,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
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-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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/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=
@@ -206,6 +237,8 @@ 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 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=
@@ -225,17 +258,25 @@ 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-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= 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 h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
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=
@@ -248,6 +289,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
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 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 +300,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,7 @@ import (
"os/exec" "os/exec"
"runtime" "runtime"
"strconv" "strconv"
"syscall"
"time" "time"
) )
@@ -187,6 +188,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())

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
} }
@@ -200,7 +215,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 +245,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 +258,9 @@ func (this *FileList) CleanPrefix(prefix string) error {
}() }()
var count = int64(10000) var count = int64(10000)
var staleLife = 600 // TODO 需要可以设置
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)+`)`, utils.UnixTime()+int64(staleLife), utils.UnixTime(), prefix)
if err != nil { if err != nil {
return err return err
} }
@@ -259,7 +275,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 +319,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 +363,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 +406,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 +421,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 +464,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 +489,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 +507,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 +523,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 +538,7 @@ ON "` + this.itemsTableName + `" (
"serverId" ASC "serverId" ASC
); );
`) `)
if err != nil { if err != nil {
// 尝试删除重建 // 尝试删除重建
if times < 3 { if times < 3 {
@@ -520,6 +553,19 @@ 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 {
remotelogs.Println("CACHE", "transfer old data from v2 to v3 failed: "+err.Error())
} else {
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 重新设置策略

View File

@@ -8,9 +8,11 @@ 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/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -55,7 +57,7 @@ type FileStorage struct {
totalSize int64 totalSize int64
list ListInterface list ListInterface
writingKeyMap map[string]bool // key => bool writingKeyMap map[string]zero.Zero // key => bool
locker sync.RWMutex locker sync.RWMutex
purgeTicker *utils.Ticker purgeTicker *utils.Ticker
@@ -68,7 +70,7 @@ type FileStorage struct {
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage { func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
return &FileStorage{ return &FileStorage{
policy: policy, policy: policy,
writingKeyMap: map[string]bool{}, writingKeyMap: map[string]zero.Zero{},
hotMap: map[string]*HotItem{}, hotMap: map[string]*HotItem{},
lastHotSize: -1, lastHotSize: -1,
} }
@@ -202,14 +204,19 @@ func (this *FileStorage) Init() 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,6 +224,17 @@ func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error
hash, path := this.keyPath(key) hash, path := this.keyPath(key)
// 检查文件记录是否已过期
if !useStale {
exists, err := this.list.Exist(hash)
if err != nil {
return nil, err
}
if !exists {
return nil, ErrNotFound
}
}
// TODO 尝试使用mmap加快读取速度 // TODO 尝试使用mmap加快读取速度
var isOk = false var isOk = false
fp, err := os.OpenFile(path, os.O_RDONLY, 0444) fp, err := os.OpenFile(path, os.O_RDONLY, 0444)
@@ -233,15 +251,6 @@ func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error
} }
}() }()
// 检查文件记录是否已过期
exists, err := this.list.Exist(hash)
if err != nil {
return nil, err
}
if !exists {
return nil, ErrNotFound
}
reader := NewFileReader(fp) reader := NewFileReader(fp)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -313,7 +322,7 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
return nil, ErrFileIsWriting return nil, ErrFileIsWriting
} }
this.locker.Lock() this.locker.Lock()
this.writingKeyMap[key] = true this.writingKeyMap[key] = zero.New()
this.locker.Unlock() this.locker.Unlock()
defer func() { defer func() {
if !isWriting { if !isWriting {
@@ -562,12 +571,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
} }
@@ -672,12 +681,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
@@ -695,26 +704,26 @@ func (this *FileStorage) initList() error {
ticker.Stop() 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
} }
@@ -794,9 +803,9 @@ func (this *FileStorage) decodeFile(path string) (*Item, error) {
// URL // URL
if urlSize > 0 { if urlSize > 0 {
data := utils.BytePool1024.Get() data := utils.BytePool1k.Get()
result, ok, err := this.readN(fp, data, int(urlSize)) result, ok, err := this.readN(fp, data, int(urlSize))
utils.BytePool1024.Put(data) utils.BytePool1k.Put(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -921,7 +930,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 +942,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
} }

View File

@@ -10,7 +10,7 @@ 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) (Writer, 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 {
@@ -157,7 +159,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)
@@ -254,7 +256,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()

View File

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

View File

@@ -8,5 +8,6 @@ var (
InTrafficBytes = uint64(0) InTrafficBytes = uint64(0)
OutTrafficBytes = uint64(0) OutTrafficBytes = uint64(0)
NodeId int64 = 0 NodeId int64 = 0
NodeIdString = ""
) )

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

@@ -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

@@ -6,6 +6,7 @@ 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/TeaOSLab/EdgeNode/internal/utils"
@@ -21,7 +22,9 @@ 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()
})
}) })
} }
@@ -60,7 +63,7 @@ func (this *CountryManager) Start() {
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
ticker.Stop() ticker.Stop()
}) })
for range ticker.C { for ticker.Next() {
err := this.loop() err := this.loop()
if err != nil { if err != nil {
remotelogs.ErrorObject("COUNTRY_MANAGER", err) remotelogs.ErrorObject("COUNTRY_MANAGER", err)

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,7 +19,9 @@ 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()
})
}) })
} }
@@ -161,7 +165,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,7 +191,7 @@ 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)

View File

@@ -6,6 +6,7 @@ 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/TeaOSLab/EdgeNode/internal/utils"
@@ -25,7 +26,9 @@ 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()
})
}) })
} }
@@ -64,7 +67,7 @@ func (this *ProvinceManager) Start() {
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
ticker.Stop() ticker.Stop()
}) })
for range ticker.C { for ticker.Next() {
err := this.loop() err := this.loop()
if err != nil { if err != nil {
remotelogs.ErrorObject("PROVINCE_MANAGER", err) remotelogs.ErrorObject("PROVINCE_MANAGER", err)

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"
@@ -34,14 +35,14 @@ func NewUpdater() *Updater {
func (this *Updater) Start() { func (this *Updater) Start() {
// 这里不需要太频繁检查更新因为通常不需要更新IP库 // 这里不需要太频繁检查更新因为通常不需要更新IP库
ticker := time.NewTicker(1 * time.Hour) ticker := time.NewTicker(1 * time.Hour)
go func() { goman.New(func() {
for range 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)
} }
} }
}() })
} }
// 单次任务 // 单次任务

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.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
isQuiting = true this.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)+"'")
// 重新读取配置 // 重新读取配置
@@ -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")
@@ -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

@@ -5,60 +5,50 @@ package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
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/ratelimit"
"github.com/TeaOSLab/EdgeNode/internal/monitor"
"github.com/iwind/TeaGo/maps"
"net" "net"
"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
hasRead 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.hasRead {
_ = this.rawConn.SetReadDeadline(time.Now().Add(5 * time.Second)) // TODO 握手超时时间可以设置
this.hasRead = 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))
} }
@@ -75,6 +65,17 @@ 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
// 全局并发数限制
this.once.Do(func() {
if this.globalLimiter != nil {
this.globalLimiter.Release()
}
})
// 单个服务并发数限制
sharedClientConnLimiter.Remove(this.rawConn.RemoteAddr().String())
return this.rawConn.Close() return this.rawConn.Close()
} }
@@ -97,7 +98,3 @@ func (this *ClientConn) SetReadDeadline(t time.Time) error {
func (this *ClientConn) SetWriteDeadline(t time.Time) error { func (this *ClientConn) SetWriteDeadline(t time.Time) error {
return this.rawConn.SetWriteDeadline(t) return this.rawConn.SetWriteDeadline(t)
} }
func (this *ClientConn) IsClosed() bool {
return this.isClosed
}

View File

@@ -0,0 +1,38 @@
// 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)
}

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() {
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 NewClientListener1(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,12 @@
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" "strings"
"strconv"
"time" "time"
) )
@@ -26,7 +27,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 +58,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 +90,55 @@ 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 = this.toValidUTF8string(accessLog.RemoteUser)
accessLog.RequestURI = this.toValidUTF8string(accessLog.RequestURI)
accessLog.RequestPath = this.toValidUTF8string(accessLog.RequestPath)
accessLog.RequestFilename = this.toValidUTF8string(accessLog.RequestFilename)
accessLog.RequestBody = bytes.ToValidUTF8(accessLog.RequestBody, []byte{})
for _, v := range accessLog.SentHeader {
for index, s := range v.Values {
v.Values[index] = this.toValidUTF8string(s)
}
}
accessLog.Referer = this.toValidUTF8string(accessLog.Referer)
accessLog.UserAgent = this.toValidUTF8string(accessLog.UserAgent)
accessLog.Request = this.toValidUTF8string(accessLog.Request)
accessLog.ContentType = this.toValidUTF8string(accessLog.ContentType)
for k, c := range accessLog.Cookie {
accessLog.Cookie[k] = this.toValidUTF8string(c)
}
accessLog.Args = this.toValidUTF8string(accessLog.Args)
accessLog.QueryString = this.toValidUTF8string(accessLog.QueryString)
for _, v := range accessLog.Header {
for index, s := range v.Values {
v.Values[index] = this.toValidUTF8string(s)
}
}
}
func (this *HTTPAccessLogQueue) toValidUTF8string(v string) string {
return strings.ToValidUTF8(v, "")
}

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
} }
@@ -171,7 +174,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: 10 * time.Second,
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
Proxy: nil, Proxy: nil,
} }

View File

@@ -1,6 +1,7 @@
package nodes package nodes
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@@ -10,8 +11,10 @@ import (
"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/types" "github.com/iwind/TeaGo/types"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@@ -25,17 +28,13 @@ 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
@@ -64,11 +63,14 @@ type HTTPRequest struct {
rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则 rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则
rewriteReplace string // 重写规则的目标 rewriteReplace string // 重写规则的目标
rewriteIsExternalURL bool // 重写目标是否为外部URL rewriteIsExternalURL bool // 重写目标是否为外部URL
cacheRef *serverconfigs.HTTPCacheRef // 缓存设置
cacheKey string // 缓存使用的Key cacheRef *serverconfigs.HTTPCacheRef // 缓存设置
isCached bool // 是否已经被缓存 cacheKey string // 缓存使用的Key
isAttack bool // 是否是攻击请求 isCached bool // 是否已经被缓存
bodyData []byte // 读取的Body内容 cacheCanTryStale bool // 是否可以尝试使用Stale缓存
isAttack bool // 是否是攻击请求
requestBodyData []byte // 读取的Body内容
// WAF相关 // WAF相关
firewallPolicyId int64 firewallPolicyId int64
@@ -107,12 +109,15 @@ func (this *HTTPRequest) init() {
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 执行请求
@@ -129,7 +134,7 @@ func (this *HTTPRequest) Do() {
// Web配置 // Web配置
err := this.configureWeb(this.Server.Web, true, 0) err := this.configureWeb(this.Server.Web, true, 0)
if err != nil { if err != nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
this.doEnd() this.doEnd()
return return
} }
@@ -200,6 +205,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 {
@@ -208,11 +235,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() {
@@ -228,7 +250,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
} }
} }
@@ -294,6 +316,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
@@ -419,6 +447,11 @@ 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
}
// 重写规则 // 重写规则
if len(web.RewriteRefs) > 0 { if len(web.RewriteRefs) > 0 {
for index, ref := range web.RewriteRefs { for index, ref := range web.RewriteRefs {
@@ -487,6 +520,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.Host) {
continue
}
if len(varMapping) > 0 { if len(varMapping) > 0 {
this.addVarMapping(varMapping) this.addVarMapping(varMapping)
} }
@@ -556,6 +594,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":
@@ -627,6 +667,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":
@@ -923,7 +968,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 {
@@ -1097,44 +1142,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.Host) {
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 需要实现
} }
} }
@@ -1159,7 +1213,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 {
@@ -1168,44 +1221,58 @@ 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.Host) {
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
@@ -1230,19 +1297,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
} }
// 检查是否可以忽略错误 // 检查是否可以忽略错误
@@ -1279,3 +1343,34 @@ func (this *HTTPRequest) canIgnore(err error) bool {
return false return false
} }
// 关闭当前连接
func (this *HTTPRequest) closeConn() {
requestConn := this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return
}
conn, ok := requestConn.(net.Conn)
if ok {
_ = conn.Close()
return
}
return
}
// 检查连接是否已关闭
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

@@ -33,7 +33,7 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
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 {

View File

@@ -5,8 +5,10 @@ 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"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -15,7 +17,9 @@ import (
) )
// 读取缓存 // 读取缓存
func (this *HTTPRequest) doCacheRead() (shouldStop bool) { func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
this.cacheCanTryStale = false
cachePolicy := this.Server.HTTPCachePolicy cachePolicy := this.Server.HTTPCachePolicy
if cachePolicy == nil || !cachePolicy.IsOn { if cachePolicy == nil || !cachePolicy.IsOn {
return return
@@ -80,6 +84,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 +113,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,12 +124,14 @@ 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() {
@@ -132,16 +145,11 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
} }
} }
} }
}() })
return true return true
} }
buf := bytePool32k.Get()
defer func() {
bytePool32k.Put(buf)
}()
var reader caches.Reader var reader caches.Reader
var err error var err error
@@ -150,16 +158,20 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
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.requestPath()), 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 { 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
} }
@@ -173,11 +185,23 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
_ = reader.Close() _ = 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 {
@@ -204,8 +228,19 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
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())
}
}
if this.web.Cache.AddAgeHeader {
this.writer.Header().Set("Age", age)
} }
// ETag // ETag
@@ -223,7 +258,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 +285,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 {
@@ -327,6 +363,8 @@ 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)
@@ -387,6 +425,8 @@ 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
} }
@@ -402,6 +442,8 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
return true, nil 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", "read from cache failed: "+err.Error())
} }
@@ -417,3 +459,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.requestFullURL() + "'" + " (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.requestFullURL() + "'" + " (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

@@ -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
} }
@@ -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
} }

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

@@ -27,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
@@ -60,17 +68,31 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
} }
} }
// 前后是否一致
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
} else { // 精准匹配 } else { // 精准匹配
if fullURL == u.RealBeforeURL() { if fullURL == u.RealBeforeURL() {
// 前后是否一致
if fullURL == u.AfterURL {
return false
}
if u.Status <= 0 { if u.Status <= 0 {
this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.RawWriter, this.RawReq, u.AfterURL, http.StatusTemporaryRedirect) http.Redirect(this.RawWriter, this.RawReq, u.AfterURL, http.StatusTemporaryRedirect)
} else { } else {
this.processResponseHeaders(u.Status)
http.Redirect(this.RawWriter, this.RawReq, u.AfterURL, u.Status) http.Redirect(this.RawWriter, this.RawReq, u.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.Server.Id, this.requestRemoteAddr(true), this.web.RequestLimit.MaxConns, this.web.RequestLimit.MaxConnsPerIP) {
this.writeCode(http.StatusTooManyRequests)
this.closeConn()
return true
}
}
}
}
return false
}

View File

@@ -7,6 +7,11 @@ import (
"time" "time"
) )
const (
// AccessLogMaxRequestBodySize 访问日志存储的请求内容最大尺寸 TODO 此值应该可以在访问日志页设置
AccessLogMaxRequestBodySize = 2 * 1024 * 1024
)
// 日志 // 日志
func (this *HTTPRequest) log() { func (this *HTTPRequest) log() {
if this.disableLog { if this.disableLog {
@@ -32,6 +37,11 @@ func (this *HTTPRequest) log() {
return return
} }
// 是否记录499
if !ref.EnableClientClosed && this.writer.StatusCode() == 499 {
return
}
addr := this.RawReq.RemoteAddr addr := this.RawReq.RemoteAddr
index := strings.LastIndex(addr, ":") index := strings.LastIndex(addr, ":")
if index > 0 { if index > 0 {
@@ -81,7 +91,7 @@ func (this *HTTPRequest) log() {
} }
accessLog := &pb.HTTPAccessLog{ accessLog := &pb.HTTPAccessLog{
RequestId: "", RequestId: this.requestId,
NodeId: sharedNodeConfig.Id, NodeId: sharedNodeConfig.Id,
ServerId: this.Server.Id, ServerId: this.Server.Id,
RemoteAddr: this.requestRemoteAddr(true), RemoteAddr: this.requestRemoteAddr(true),
@@ -135,6 +145,15 @@ func (this *HTTPRequest) log() {
accessLog.OriginAddress = this.originAddr accessLog.OriginAddress = this.originAddr
} }
// 请求Body
if ref.ContainsField(serverconfigs.HTTPAccessLogFieldRequestBody) {
accessLog.RequestBody = this.requestBodyData
if len(accessLog.RequestBody) > AccessLogMaxRequestBodySize {
accessLog.RequestBody = accessLog.RequestBody[:AccessLogMaxRequestBodySize]
}
}
// TODO 记录匹配的 locationId和rewriteId // TODO 记录匹配的 locationId和rewriteId
sharedHTTPAccessLogQueue.Push(accessLog) sharedHTTPAccessLogQueue.Push(accessLog)

View File

@@ -68,11 +68,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
this.writer.Prepare(stat.Size(), status) this.writer.Prepare(stat.Size(), status)
this.writer.WriteHeader(status) this.writer.WriteHeader(status)
} }
buf := bytePool1k.Get() buf := utils.BytePool1k.Get()
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte { _, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
return []byte(this.Format(string(p))) return []byte(this.Format(string(p)))
}) })
bytePool1k.Put(buf) utils.BytePool1k.Put(buf)
if err != nil { if err != nil {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_PAGE", "write to client failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_PAGE", "write to client failed: "+err.Error())
@@ -88,6 +88,12 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
return true return true
} else if page.BodyType == shared.BodyTypeHTML { } else if page.BodyType == shared.BodyTypeHTML {
// 这里需要实现设置Status因为在Format()中可以获取${status}等变量
if page.NewStatus > 0 {
this.writer.statusCode = page.NewStatus
} else {
this.writer.statusCode = status
}
var content = this.Format(page.Body) var content = this.Format(page.Body)
// 修改状态码 // 修改状态码

View File

@@ -15,5 +15,5 @@ func (this *HTTPRequest) doPlanExpires() {
this.processResponseHeaders(statusCode) this.processResponseHeaders(statusCode)
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
_, _ = this.writer.WriteString(serverconfigs.DefaultPlanExpireNoticePageBody) _, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))
} }

View File

@@ -42,6 +42,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
} }
newURL := "https://" + host + this.RawReq.RequestURI newURL := "https://" + host + this.RawReq.RequestURI
this.processResponseHeaders(statusCode)
http.Redirect(this.writer, this.RawReq, newURL, statusCode) http.Redirect(this.writer, this.RawReq, newURL, statusCode)
return true return true

View File

@@ -40,8 +40,8 @@ func (this *HTTPRequest) doReverseProxy() {
requestCall.CallResponseCallbacks(this.writer) requestCall.CallResponseCallbacks(this.writer)
if origin == nil { if origin == nil {
err := errors.New(this.requestFullURL() + ": no available origin sites for reverse proxy") err := errors.New(this.requestFullURL() + ": no available origin sites for reverse proxy")
remotelogs.ServerError(this.Server.Id, "HTTP_REQUEST_REVERSE_PROXY", err.Error()) remotelogs.ServerError(this.Server.Id, "HTTP_REQUEST_REVERSE_PROXY", err.Error(), "", nil)
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, true)
return return
} }
this.origin = origin // 设置全局变量是为了日志等处理 this.origin = origin // 设置全局变量是为了日志等处理
@@ -61,7 +61,7 @@ func (this *HTTPRequest) doReverseProxy() {
if origin.Addr == nil { if origin.Addr == nil {
err := errors.New(this.requestFullURL() + ": origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address") err := errors.New(this.requestFullURL() + ": origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error()) remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, true)
return return
} }
this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme() this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme()
@@ -117,7 +117,15 @@ func (this *HTTPRequest) doReverseProxy() {
} }
this.RawReq.URL.Host = this.RawReq.Host this.RawReq.URL.Host = this.RawReq.Host
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin { } else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
this.RawReq.Host = originAddr // 源站主机名
var hostname = originAddr
if origin.Addr.Protocol.IsHTTPFamily() {
hostname = strings.TrimSuffix(hostname, ":80")
} else if origin.Addr.Protocol.IsHTTPSFamily() {
hostname = strings.TrimSuffix(hostname, ":443")
}
this.RawReq.Host = hostname
this.RawReq.URL.Host = this.RawReq.Host this.RawReq.URL.Host = this.RawReq.Host
} else { } else {
this.RawReq.URL.Host = this.Host this.RawReq.URL.Host = this.Host
@@ -148,7 +156,7 @@ func (this *HTTPRequest) doReverseProxy() {
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol) client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error()) remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, true)
return return
} }
@@ -167,18 +175,18 @@ func (this *HTTPRequest) doReverseProxy() {
SharedOriginStateManager.Fail(origin, this.reverseProxy, func() { SharedOriginStateManager.Fail(origin, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling() this.reverseProxy.ResetScheduling()
}) })
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, true)
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+"': "+err.Error()) remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+"': "+err.Error())
} else if httpErr.Err != context.Canceled { } else if httpErr.Err != context.Canceled {
SharedOriginStateManager.Fail(origin, this.reverseProxy, func() { SharedOriginStateManager.Fail(origin, this.reverseProxy, func() {
this.reverseProxy.ResetScheduling() this.reverseProxy.ResetScheduling()
}) })
if httpErr.Timeout() { if httpErr.Timeout() {
this.write50x(err, http.StatusGatewayTimeout) this.write50x(err, http.StatusGatewayTimeout, true)
} else if httpErr.Temporary() { } else if httpErr.Temporary() {
this.write50x(err, http.StatusServiceUnavailable) this.write50x(err, http.StatusServiceUnavailable, true)
} else { } else {
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, true)
} }
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+"': "+err.Error()) remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+"': "+err.Error())
} else { } else {
@@ -186,6 +194,12 @@ func (this *HTTPRequest) doReverseProxy() {
isClientError := false isClientError := false
if ok { if ok {
if httpErr.Err == context.Canceled { if httpErr.Err == context.Canceled {
// 如果是服务器端主动关闭,则无需提示
if this.isConnClosed() {
this.disableLog = true
return
}
isClientError = true isClientError = true
this.addError(errors.New(httpErr.Op + " " + httpErr.URL + ": client closed the connection")) this.addError(errors.New(httpErr.Op + " " + httpErr.URL + ": client closed the connection"))
this.writer.WriteHeader(499) // 仿照nginx this.writer.WriteHeader(499) // 仿照nginx
@@ -193,7 +207,7 @@ func (this *HTTPRequest) doReverseProxy() {
} }
if !isClientError { if !isClientError {
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, true)
} }
} }
if resp != nil && resp.Body != nil { if resp != nil && resp.Body != nil {
@@ -218,8 +232,6 @@ func (this *HTTPRequest) doReverseProxy() {
} }
} }
// TODO 清除源站错误次数
// 特殊页面 // 特殊页面
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) { if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
err = resp.Body.Close() err = resp.Body.Close()
@@ -294,7 +306,7 @@ func (this *HTTPRequest) doReverseProxy() {
} }
// 是否成功结束 // 是否成功结束
if err == nil && closeErr == nil { if (err == nil || err == io.EOF) && (closeErr == nil || closeErr == io.EOF) {
this.writer.SetOk() this.writer.SetOk()
} }
} }

View File

@@ -30,8 +30,10 @@ func (this *HTTPRequest) doRewrite() (shouldShop bool) {
// 跳转 // 跳转
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect { if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
if this.rewriteRule.RedirectStatus > 0 { if this.rewriteRule.RedirectStatus > 0 {
this.processResponseHeaders(this.rewriteRule.RedirectStatus)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus) http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
} else { } else {
this.processResponseHeaders(http.StatusTemporaryRedirect)
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect) http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
} }
return true return true

View File

@@ -2,6 +2,7 @@ package nodes
import ( import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/cespare/xxhash" "github.com/cespare/xxhash"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
@@ -17,23 +18,23 @@ import (
) )
// 文本mime-type列表 // 文本mime-type列表
var textMimeMap = map[string]bool{ var textMimeMap = map[string]zero.Zero{
"application/atom+xml": true, "application/atom+xml": {},
"application/javascript": true, "application/javascript": {},
"application/x-javascript": true, "application/x-javascript": {},
"application/json": true, "application/json": {},
"application/rss+xml": true, "application/rss+xml": {},
"application/x-web-app-manifest+json": true, "application/x-web-app-manifest+json": {},
"application/xhtml+xml": true, "application/xhtml+xml": {},
"application/xml": true, "application/xml": {},
"image/svg+xml": true, "image/svg+xml": {},
"text/css": true, "text/css": {},
"text/plain": true, "text/plain": {},
"text/javascript": true, "text/javascript": {},
"text/xml": true, "text/xml": {},
"text/html": true, "text/html": {},
"text/xhtml": true, "text/xhtml": {},
"text/sgml": true, "text/sgml": {},
} }
// 调用本地静态资源 // 调用本地静态资源
@@ -109,7 +110,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
} }
return return
} else { } else {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, true)
logs.Error(err) logs.Error(err)
return true return true
} }
@@ -138,7 +139,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
} }
return return
} else { } else {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, true)
logs.Error(err) logs.Error(err)
return true return true
} }
@@ -283,7 +284,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
reader, err := os.OpenFile(filePath, os.O_RDONLY, 0444) reader, err := os.OpenFile(filePath, os.O_RDONLY, 0444)
if err != nil { if err != nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, true)
return true return true
} }

View File

@@ -64,11 +64,11 @@ func (this *HTTPRequest) doShutdown() {
this.processResponseHeaders(http.StatusOK) this.processResponseHeaders(http.StatusOK)
this.writer.WriteHeader(http.StatusOK) this.writer.WriteHeader(http.StatusOK)
} }
buf := bytePool1k.Get() buf := utils.BytePool1k.Get()
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte { _, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
return []byte(this.Format(string(p))) return []byte(this.Format(string(p)))
}) })
bytePool1k.Put(buf) utils.BytePool1k.Put(buf)
if err != nil { if err != nil {
if !this.canIgnore(err) { if !this.canIgnore(err) {
remotelogs.Warn("HTTP_REQUEST_SHUTDOWN", "write to client failed: "+err.Error()) remotelogs.Warn("HTTP_REQUEST_SHUTDOWN", "write to client failed: "+err.Error())

View File

@@ -1,6 +1,8 @@
package nodes package nodes
import "github.com/TeaOSLab/EdgeNode/internal/stats" import (
"github.com/TeaOSLab/EdgeNode/internal/stats"
)
// 统计 // 统计
func (this *HTTPRequest) doStat() { func (this *HTTPRequest) doStat() {
@@ -9,6 +11,6 @@ func (this *HTTPRequest) doStat() {
} }
// 内置的统计 // 内置的统计
stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.Server.Id, this.requestRemoteAddr(true)) stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.Server.Id, this.requestRemoteAddr(true), this.writer.SentBodyBytes(), this.isAttack)
stats.SharedHTTPRequestStatManager.AddUserAgent(this.Server.Id, this.requestHeader("User-Agent")) stats.SharedHTTPRequestStatManager.AddUserAgent(this.Server.Id, this.requestHeader("User-Agent"))
} }

View File

@@ -3,6 +3,7 @@ package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"
"runtime"
"testing" "testing"
) )
@@ -33,3 +34,17 @@ func TestHTTPRequest_RedirectToHTTPS(t *testing.T) {
a.IsBool(req.web.RedirectToHttps.IsOn == true) a.IsBool(req.web.RedirectToHttps.IsOn == true)
} }
} }
func TestHTTPRequest_Memory(t *testing.T) {
var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1)
var requests = []*HTTPRequest{}
for i := 0; i < 1_000_000; i++ {
requests = append(requests, &HTTPRequest{})
}
var stat2 = &runtime.MemStats{}
runtime.ReadMemStats(stat2)
t.Log((stat2.HeapInuse-stat1.HeapInuse)/1024/1024, "MB,")
}

View File

@@ -17,8 +17,8 @@ func (this *HTTPRequest) doTrafficLimit() {
this.writer.WriteHeader(statusCode) this.writer.WriteHeader(statusCode)
if len(config.NoticePageBody) != 0 { if len(config.NoticePageBody) != 0 {
_, _ = this.writer.WriteString(config.NoticePageBody) _, _ = this.writer.WriteString(this.Format(config.NoticePageBody))
} else { } else {
_, _ = this.writer.WriteString(serverconfigs.DefaultTrafficLimitNoticePageBody) _, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultTrafficLimitNoticePageBody))
} }
} }

View File

@@ -35,7 +35,7 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_URL", req.URL.String()+": "+err.Error()) remotelogs.Error("HTTP_REQUEST_URL", req.URL.String()+": "+err.Error())
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
return return
} }
defer func() { defer func() {

View File

@@ -3,9 +3,13 @@ package nodes
import ( import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
"net/http"
"strconv" "strconv"
"strings" "strings"
"sync/atomic"
) )
// 分解Range // 分解Range
@@ -125,3 +129,27 @@ func httpRequestGenBoundary() string {
} }
return fmt.Sprintf("%x", buf[:]) return fmt.Sprintf("%x", buf[:])
} }
// 判断状态是否为跳转
func httpStatusIsRedirect(statusCode int) bool {
return statusCode == http.StatusPermanentRedirect ||
statusCode == http.StatusTemporaryRedirect ||
statusCode == http.StatusMovedPermanently ||
statusCode == http.StatusSeeOther ||
statusCode == http.StatusFound
}
// 生成请求ID
var httpRequestTimestamp int64
var httpRequestId int32 = 1_000_000
func httpRequestNextId() string {
var unixTime = utils.UnixTimeMilli()
if unixTime > httpRequestTimestamp {
atomic.StoreInt32(&httpRequestId, 1_000_000)
httpRequestTimestamp = unixTime
}
// timestamp + requestId + nodeId
return strconv.FormatInt(unixTime, 10) + teaconst.NodeIdString + strconv.Itoa(int(atomic.AddInt32(&httpRequestId, 1)))
}

View File

@@ -1,8 +1,13 @@
package nodes package nodes
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"
"runtime"
"sync"
"testing" "testing"
"time"
) )
func TestHTTPRequest_httpRequestParseContentRange(t *testing.T) { func TestHTTPRequest_httpRequestParseContentRange(t *testing.T) {
@@ -53,3 +58,60 @@ func TestHTTPRequest_httpRequestParseContentRange(t *testing.T) {
t.Log(set) t.Log(set)
} }
} }
func TestHTTPRequest_httpRequestNextId(t *testing.T) {
teaconst.NodeId = 123
teaconst.NodeIdString = "123"
t.Log(httpRequestNextId())
t.Log(httpRequestNextId())
t.Log(httpRequestNextId())
time.Sleep(1 * time.Second)
t.Log(httpRequestNextId())
t.Log(httpRequestNextId())
time.Sleep(1 * time.Second)
t.Log(httpRequestNextId())
}
func TestHTTPRequest_httpRequestNextId_Concurrent(t *testing.T) {
var m = map[string]zero.Zero{}
var locker = sync.Mutex{}
var count = 4000
var wg = &sync.WaitGroup{}
wg.Add(count)
var countDuplicated = 0
for i := 0; i < count; i++ {
go func() {
defer wg.Done()
var requestId = httpRequestNextId()
locker.Lock()
_, ok := m[requestId]
if ok {
t.Log("duplicated:", requestId)
countDuplicated++
}
m[requestId] = zero.New()
locker.Unlock()
}()
}
wg.Wait()
t.Log("ok", countDuplicated, "duplicated")
var a = assert.NewAssertion(t)
a.IsTrue(countDuplicated == 0)
}
func BenchmarkHTTPRequest_httpRequestNextId(b *testing.B) {
runtime.GOMAXPROCS(1)
teaconst.NodeIdString = "123"
for i := 0; i < b.N; i++ {
_ = httpRequestNextId()
}
}

View File

@@ -7,41 +7,40 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
) )
// 调用WAF // 调用WAF
func (this *HTTPRequest) doWAFRequest() (blocked bool) { func (this *HTTPRequest) doWAFRequest() (blocked bool) {
var remoteAddr = this.requestRemoteAddr(true)
// 检查是否为白名单直连
if !Tea.IsTesting() && sharedNodeConfig.IPIsAutoAllowed(remoteAddr) {
return
}
// 当前连接是否已关闭 // 当前连接是否已关闭
var conn = this.RawReq.Context().Value(HTTPConnContextKey) if this.isConnClosed() {
if conn != nil { this.disableLog = true
if isClientConnClosed(conn.(net.Conn)) { return true
this.disableLog = true
return true
}
} }
// 是否在全局名单中 // 是否在全局名单中
var remoteAddr = this.requestRemoteAddr(true)
if !iplibrary.AllowIP(remoteAddr, this.Server.Id) { if !iplibrary.AllowIP(remoteAddr, this.Server.Id) {
this.disableLog = true this.disableLog = true
if conn != nil { this.closeConn()
_ = conn.(net.Conn).Close()
}
return true return true
} }
// 检查是否在临时黑名单中 // 检查是否在临时黑名单中
if waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeService, this.Server.Id, remoteAddr) || waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteAddr) { if waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeService, this.Server.Id, remoteAddr) || waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteAddr) {
this.disableLog = true this.disableLog = true
if conn != nil { this.closeConn()
_ = conn.(net.Conn).Close()
}
return true return true
} }
@@ -190,17 +189,11 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
return return
} }
w.OnAction(func(action waf.ActionInterface) (goNext bool) {
switch action.Code() {
case waf.ActionTag:
this.tags = action.(*waf.TagAction).Tags
}
return true
})
goNext, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer) goNext, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_WAF", this.rawURI+": "+err.Error()) if !this.canIgnore(err) {
remotelogs.Error("HTTP_REQUEST_WAF", this.rawURI+": "+err.Error())
}
return return
} }
@@ -254,17 +247,11 @@ func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFi
return return
} }
w.OnAction(func(action waf.ActionInterface) (goNext bool) {
switch action.Code() {
case waf.ActionTag:
this.tags = action.(*waf.TagAction).Tags
}
return true
})
goNext, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer) goNext, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer)
if err != nil { if err != nil {
remotelogs.Error("HTTP_REQUEST_WAF", this.rawURI+": "+err.Error()) if !this.canIgnore(err) {
remotelogs.Error("HTTP_REQUEST_WAF", this.rawURI+": "+err.Error())
}
return return
} }
@@ -300,12 +287,12 @@ func (this *HTTPRequest) WAFRemoteIP() string {
// WAFGetCacheBody 获取缓存中的Body // WAFGetCacheBody 获取缓存中的Body
func (this *HTTPRequest) WAFGetCacheBody() []byte { func (this *HTTPRequest) WAFGetCacheBody() []byte {
return this.bodyData return this.requestBodyData
} }
// WAFSetCacheBody 设置Body // WAFSetCacheBody 设置Body
func (this *HTTPRequest) WAFSetCacheBody(body []byte) { func (this *HTTPRequest) WAFSetCacheBody(body []byte) {
this.bodyData = body this.requestBodyData = body
} }
// WAFReadBody 读取Body // WAFReadBody 读取Body
@@ -313,16 +300,14 @@ func (this *HTTPRequest) WAFReadBody(max int64) (data []byte, err error) {
if this.RawReq.ContentLength > 0 { if this.RawReq.ContentLength > 0 {
data, err = ioutil.ReadAll(io.LimitReader(this.RawReq.Body, max)) data, err = ioutil.ReadAll(io.LimitReader(this.RawReq.Body, max))
} }
return return
} }
// WAFRestoreBody 恢复Body // WAFRestoreBody 恢复Body
func (this *HTTPRequest) WAFRestoreBody(data []byte) { func (this *HTTPRequest) WAFRestoreBody(data []byte) {
if len(data) > 0 { if len(data) > 0 {
rawReader := bytes.NewBuffer(data) this.RawReq.Body = ioutil.NopCloser(io.MultiReader(bytes.NewBuffer(data), this.RawReq.Body))
buf := make([]byte, 1024)
_, _ = io.CopyBuffer(rawReader, this.RawReq.Body, buf)
this.RawReq.Body = ioutil.NopCloser(rawReader)
} }
} }
@@ -333,14 +318,22 @@ func (this *HTTPRequest) WAFServerId() int64 {
// WAFClose 关闭连接 // WAFClose 关闭连接
func (this *HTTPRequest) WAFClose() { func (this *HTTPRequest) WAFClose() {
requestConn := this.RawReq.Context().Value(HTTPConnContextKey) this.closeConn()
if requestConn == nil { }
return
} func (this *HTTPRequest) WAFOnAction(action interface{}) (goNext bool) {
conn, ok := requestConn.(net.Conn) if action == nil {
if ok { return true
_ = conn.Close() }
return
} instance, ok := action.(waf.ActionInterface)
return if !ok {
return true
}
switch instance.Code() {
case waf.ActionTag:
this.tags = append(this.tags, action.(*waf.TagAction).Tags...)
}
return true
} }

View File

@@ -2,6 +2,8 @@ package nodes
import ( import (
"errors" "errors"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@@ -42,7 +44,7 @@ func (this *HTTPRequest) doWebsocket() {
// TODO 增加N次错误重试重试的时候需要尝试不同的源站 // TODO 增加N次错误重试重试的时候需要尝试不同的源站
originConn, err := OriginConnect(this.origin, this.RawReq.RemoteAddr) originConn, err := OriginConnect(this.origin, this.RawReq.RemoteAddr)
if err != nil { if err != nil {
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, false)
return return
} }
defer func() { defer func() {
@@ -51,21 +53,22 @@ func (this *HTTPRequest) doWebsocket() {
err = this.RawReq.Write(originConn) err = this.RawReq.Write(originConn)
if err != nil { if err != nil {
this.write50x(err, http.StatusBadGateway) this.write50x(err, http.StatusBadGateway, false)
return return
} }
clientConn, _, err := this.writer.Hijack() clientConn, _, err := this.writer.Hijack()
if err != nil || clientConn == nil { if err != nil || clientConn == nil {
this.write50x(err, http.StatusInternalServerError) this.write50x(err, http.StatusInternalServerError, false)
return return
} }
defer func() { defer func() {
_ = clientConn.Close() _ = clientConn.Close()
}() }()
go func() { goman.New(func() {
buf := make([]byte, 4*1024) // TODO 使用内存池 var buf = utils.BytePool4k.Get()
defer utils.BytePool4k.Put(buf)
for { for {
n, err := originConn.Read(buf) n, err := originConn.Read(buf)
if n > 0 { if n > 0 {
@@ -81,6 +84,6 @@ func (this *HTTPRequest) doWebsocket() {
} }
_ = clientConn.Close() _ = clientConn.Close()
_ = originConn.Close() _ = originConn.Close()
}() })
_, _ = io.Copy(originConn, clientConn) _, _ = io.Copy(originConn, clientConn)
} }

View File

@@ -1,3 +1,5 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes package nodes
import ( import (
@@ -76,22 +78,6 @@ func NewHTTPWriter(req *HTTPRequest, httpResponseWriter http.ResponseWriter) *HT
} }
} }
// Reset 重置
func (this *HTTPWriter) Reset(httpResponseWriter http.ResponseWriter) {
this.writer = httpResponseWriter
this.compressionConfig = nil
this.compressionWriter = nil
this.statusCode = 0
this.sentBodyBytes = 0
this.bodyCopying = false
this.body = nil
this.compressionBodyBuffer = nil
this.compressionBodyWriter = nil
}
// SetCompression 设置内容压缩配置 // SetCompression 设置内容压缩配置
func (this *HTTPWriter) SetCompression(config *serverconfigs.HTTPCompressionConfig) { func (this *HTTPWriter) SetCompression(config *serverconfigs.HTTPCompressionConfig) {
this.compressionConfig = config this.compressionConfig = config
@@ -118,6 +104,14 @@ func (this *HTTPWriter) Prepare(size int64, status int) (delayHeaders bool) {
this.PrepareCompression(size) this.PrepareCompression(size)
} }
// 是否限速写入
if this.req.web != nil &&
this.req.web.RequestLimit != nil &&
this.req.web.RequestLimit.IsOn &&
this.req.web.RequestLimit.OutBandwidthPerConnBytes() > 0 {
this.writer = NewHTTPRateWriter(this.writer, this.req.web.RequestLimit.OutBandwidthPerConnBytes())
}
return return
} }
@@ -421,10 +415,12 @@ func (this *HTTPWriter) Close() {
if this.isOk { if this.isOk {
err := this.cacheWriter.Close() err := this.cacheWriter.Close()
if err == nil { if err == nil {
var expiredAt = this.cacheWriter.ExpiredAt()
this.cacheStorage.AddToList(&caches.Item{ this.cacheStorage.AddToList(&caches.Item{
Type: this.cacheWriter.ItemType(), Type: this.cacheWriter.ItemType(),
Key: this.cacheWriter.Key(), Key: this.cacheWriter.Key(),
ExpiredAt: this.cacheWriter.ExpiredAt(), ExpiredAt: expiredAt,
StaleAt: expiredAt + int64(this.calculateStaleLife()),
HeaderSize: this.cacheWriter.HeaderSize(), HeaderSize: this.cacheWriter.HeaderSize(),
BodySize: this.cacheWriter.BodySize(), BodySize: this.cacheWriter.BodySize(),
Host: this.req.Host, Host: this.req.Host,
@@ -566,6 +562,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
// 不支持Range // 不支持Range
if len(this.Header().Get("Content-Range")) > 0 { if len(this.Header().Get("Content-Range")) > 0 {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, not supported Content-Range") this.Header().Set("X-Cache", "BYPASS, not supported Content-Range")
} }
@@ -574,6 +571,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
// 如果允许 ChunkedEncoding就无需尺寸的判断因为此时的 size 为 -1 // 如果允许 ChunkedEncoding就无需尺寸的判断因为此时的 size 为 -1
if !cacheRef.AllowChunkedEncoding && size < 0 { if !cacheRef.AllowChunkedEncoding && size < 0 {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, ChunkedEncoding") this.Header().Set("X-Cache", "BYPASS, ChunkedEncoding")
} }
@@ -581,6 +579,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
} }
if size >= 0 && ((cacheRef.MaxSizeBytes() > 0 && size > cacheRef.MaxSizeBytes()) || if size >= 0 && ((cacheRef.MaxSizeBytes() > 0 && size > cacheRef.MaxSizeBytes()) ||
(cachePolicy.MaxSizeBytes() > 0 && size > cachePolicy.MaxSizeBytes()) || (cacheRef.MinSizeBytes() > size)) { (cachePolicy.MaxSizeBytes() > 0 && size > cachePolicy.MaxSizeBytes()) || (cacheRef.MinSizeBytes() > size)) {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, Content-Length") this.Header().Set("X-Cache", "BYPASS, Content-Length")
} }
@@ -589,6 +588,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
// 检查状态 // 检查状态
if len(cacheRef.Status) > 0 && !lists.ContainsInt(cacheRef.Status, this.StatusCode()) { if len(cacheRef.Status) > 0 && !lists.ContainsInt(cacheRef.Status, this.StatusCode()) {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, Status: "+types.String(this.StatusCode())) this.Header().Set("X-Cache", "BYPASS, Status: "+types.String(this.StatusCode()))
} }
@@ -602,6 +602,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
values := strings.Split(cacheControl, ",") values := strings.Split(cacheControl, ",")
for _, value := range values { for _, value := range values {
if cacheRef.ContainsCacheControl(strings.TrimSpace(value)) { if cacheRef.ContainsCacheControl(strings.TrimSpace(value)) {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, Cache-Control: "+cacheControl) this.Header().Set("X-Cache", "BYPASS, Cache-Control: "+cacheControl)
} }
@@ -613,6 +614,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
// Set-Cookie // Set-Cookie
if cacheRef.SkipResponseSetCookie && len(this.writer.Header().Get("Set-Cookie")) > 0 { if cacheRef.SkipResponseSetCookie && len(this.writer.Header().Get("Set-Cookie")) > 0 {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, Set-Cookie") this.Header().Set("X-Cache", "BYPASS, Set-Cookie")
} }
@@ -621,6 +623,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
// 校验其他条件 // 校验其他条件
if cacheRef.Conds != nil && cacheRef.Conds.HasResponseConds() && !cacheRef.Conds.MatchResponse(this.req.Format) { if cacheRef.Conds != nil && cacheRef.Conds.HasResponseConds() && !cacheRef.Conds.MatchResponse(this.req.Format) {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, ResponseConds") this.Header().Set("X-Cache", "BYPASS, ResponseConds")
} }
@@ -630,17 +633,40 @@ func (this *HTTPWriter) prepareCache(size int64) {
// 打开缓存写入 // 打开缓存写入
storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id) storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
if storage == nil { if storage == nil {
this.req.varMapping["cache.status"] = "BYPASS"
if addStatusHeader { if addStatusHeader {
this.Header().Set("X-Cache", "BYPASS, Storage") this.Header().Set("X-Cache", "BYPASS, Storage")
} }
return return
} }
this.req.varMapping["cache.status"] = "UPDATING"
if addStatusHeader {
this.Header().Set("X-Cache", "UPDATING")
}
this.cacheStorage = storage this.cacheStorage = storage
life := cacheRef.LifeSeconds() life := cacheRef.LifeSeconds()
if life <= 60 { // 最小不能少于1分钟
if life <= 0 {
life = 60 life = 60
} }
// 支持源站设置的max-age
if this.req.web.Cache != nil && this.req.web.Cache.EnableCacheControlMaxAge {
var cacheControl = this.Header().Get("Cache-Control")
var pieces = strings.Split(cacheControl, ";")
for _, piece := range pieces {
var eqIndex = strings.Index(piece, "=")
if eqIndex > 0 && piece[:eqIndex] == "max-age" {
var maxAge = types.Int64(piece[eqIndex+1:])
if maxAge > 0 {
life = maxAge
}
}
}
}
expiredAt := utils.UnixTime() + life expiredAt := utils.UnixTime() + life
var cacheKey = this.req.cacheKey var cacheKey = this.req.cacheKey
if this.webpIsEncoding { if this.webpIsEncoding {
@@ -668,3 +694,32 @@ func (this *HTTPWriter) prepareCache(size int64) {
} }
} }
} }
// 计算stale时长
func (this *HTTPWriter) calculateStaleLife() int {
var staleLife = 600 // TODO 可以在缓存策略里设置此时间
var staleConfig = this.req.web.Cache.Stale
if staleConfig != nil && staleConfig.IsOn {
// 从Header中读取stale-if-error
var isDefinedInHeader = false
if staleConfig.SupportStaleIfErrorHeader {
var cacheControl = this.Header().Get("Cache-Control")
var pieces = strings.Split(cacheControl, ",")
for _, piece := range pieces {
var eqIndex = strings.Index(piece, "=")
if eqIndex > 0 && strings.TrimSpace(piece[:eqIndex]) == "stale-if-error" {
// 这里预示着如果stale-if-error=0可以关闭stale功能
staleLife = types.Int(strings.TrimSpace(piece[eqIndex+1:]))
isDefinedInHeader = true
break
}
}
}
// 自定义
if !isDefinedInHeader && staleConfig.Life != nil {
staleLife = types.Int(staleConfig.Life.Duration().Seconds())
}
}
return staleLife
}

View File

@@ -0,0 +1,102 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"bufio"
"github.com/iwind/TeaGo/types"
"net"
"net/http"
"time"
)
// HTTPRateWriter 限速写入
type HTTPRateWriter struct {
parentWriter http.ResponseWriter
rateBytes int
lastBytes int
timeCost time.Duration
}
func NewHTTPRateWriter(writer http.ResponseWriter, rateBytes int64) http.ResponseWriter {
return &HTTPRateWriter{
parentWriter: writer,
rateBytes: types.Int(rateBytes),
}
}
func (this *HTTPRateWriter) Header() http.Header {
return this.parentWriter.Header()
}
func (this *HTTPRateWriter) Write(data []byte) (int, error) {
if len(data) == 0 {
return 0, nil
}
var left = this.rateBytes - this.lastBytes
if left <= 0 {
if this.timeCost > 0 && this.timeCost < 1*time.Second {
time.Sleep(1*time.Second - this.timeCost)
}
this.lastBytes = 0
this.timeCost = 0
return this.Write(data)
}
var n = len(data)
// n <= left
if n <= left {
this.lastBytes += n
var before = time.Now()
defer func() {
this.timeCost += time.Since(before)
}()
return this.parentWriter.Write(data)
}
// n > left
var before = time.Now()
result, err := this.parentWriter.Write(data[:left])
this.timeCost += time.Since(before)
if err != nil {
return result, err
}
this.lastBytes += left
return this.Write(data[left:])
}
func (this *HTTPRateWriter) WriteHeader(statusCode int) {
this.parentWriter.WriteHeader(statusCode)
}
// Hijack Hijack
func (this *HTTPRateWriter) Hijack() (conn net.Conn, buf *bufio.ReadWriter, err error) {
if this.parentWriter == nil {
return
}
hijack, ok := this.parentWriter.(http.Hijacker)
if ok {
return hijack.Hijack()
}
return
}
// Flush Flush
func (this *HTTPRateWriter) Flush() {
if this.parentWriter == nil {
return
}
flusher, ok := this.parentWriter.(http.Flusher)
if ok {
flusher.Flush()
return
}
}

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"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"
"net" "net"
"sync" "sync"
@@ -55,11 +56,11 @@ func (this *Listener) listenTCP() error {
} }
protocol := this.group.Protocol() protocol := this.group.Protocol()
netListener, err := this.createTCPListener() tcpListener, err := this.createTCPListener()
if err != nil { if err != nil {
return err return err
} }
netListener = NewClientListener(netListener, protocol.IsHTTPFamily() || protocol.IsHTTPSFamily()) var netListener = NewClientListener1(tcpListener, protocol.IsHTTPFamily() || protocol.IsHTTPSFamily())
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr()) remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
_ = netListener.Close() _ = netListener.Close()
@@ -72,6 +73,7 @@ func (this *Listener) listenTCP() error {
Listener: netListener, Listener: netListener,
} }
case serverconfigs.ProtocolHTTPS, serverconfigs.ProtocolHTTPS4, serverconfigs.ProtocolHTTPS6: case serverconfigs.ProtocolHTTPS, serverconfigs.ProtocolHTTPS4, serverconfigs.ProtocolHTTPS6:
netListener.SetIsTLS(true)
this.listener = &HTTPListener{ this.listener = &HTTPListener{
BaseListener: BaseListener{Group: this.group}, BaseListener: BaseListener{Group: this.group},
Listener: netListener, Listener: netListener,
@@ -82,6 +84,7 @@ func (this *Listener) listenTCP() error {
Listener: netListener, Listener: netListener,
} }
case serverconfigs.ProtocolTLS, serverconfigs.ProtocolTLS4, serverconfigs.ProtocolTLS6: case serverconfigs.ProtocolTLS, serverconfigs.ProtocolTLS4, serverconfigs.ProtocolTLS6:
netListener.SetIsTLS(true)
this.listener = &TCPListener{ this.listener = &TCPListener{
BaseListener: BaseListener{Group: this.group}, BaseListener: BaseListener{Group: this.group},
Listener: netListener, Listener: netListener,
@@ -97,7 +100,7 @@ func (this *Listener) listenTCP() error {
this.listener.Init() this.listener.Init()
go func() { goman.New(func() {
err := this.listener.Serve() err := this.listener.Serve()
if err != nil { if err != nil {
// 在这里屏蔽accept错误防止在优雅关闭的时候有多余的提示 // 在这里屏蔽accept错误防止在优雅关闭的时候有多余的提示
@@ -109,7 +112,7 @@ func (this *Listener) listenTCP() error {
// 打印其他错误 // 打印其他错误
remotelogs.Error("LISTENER", err.Error()) remotelogs.Error("LISTENER", err.Error())
} }
}() })
return nil return nil
} }
@@ -129,12 +132,12 @@ func (this *Listener) listenUDP() error {
Listener: listener, Listener: listener,
} }
go func() { goman.New(func() {
err := this.listener.Serve() err := this.listener.Serve()
if err != nil { if err != nil {
remotelogs.Error("LISTENER", err.Error()) remotelogs.Error("LISTENER", err.Error())
} }
}() })
return nil return nil
} }

View File

@@ -8,7 +8,6 @@ import (
"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/types" "github.com/iwind/TeaGo/types"
"golang.org/x/net/http2"
) )
type BaseListener struct { type BaseListener struct {
@@ -35,48 +34,21 @@ func (this *BaseListener) CountActiveListeners() int {
func (this *BaseListener) buildTLSConfig() *tls.Config { func (this *BaseListener) buildTLSConfig() *tls.Config {
return &tls.Config{ return &tls.Config{
Certificates: nil, Certificates: nil,
GetConfigForClient: func(info *tls.ClientHelloInfo) (config *tls.Config, e error) { GetConfigForClient: func(configInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
ssl, _, err := this.matchSSL(info.ServerName) ssl, _, err := this.matchSSL(configInfo.ServerName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cipherSuites := ssl.TLSCipherSuites() return ssl.TLSConfig(), nil
if !ssl.CipherSuitesIsOn || len(cipherSuites) == 0 {
cipherSuites = nil
}
nextProto := []string{}
if ssl.HTTP2Enabled {
nextProto = []string{http2.NextProtoTLS}
}
return &tls.Config{
Certificates: nil,
MinVersion: ssl.TLSMinVersion(),
CipherSuites: cipherSuites,
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
_, cert, err := this.matchSSL(info.ServerName)
if err != nil {
return nil, err
}
if cert == nil {
return nil, errors.New("no ssl certs found for '" + info.ServerName + "'")
}
return cert, nil
},
ClientAuth: sslconfigs.GoSSLClientAuthType(ssl.ClientAuthType),
ClientCAs: ssl.CAPool(),
NextProtos: nextProto,
}, nil
}, },
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) { GetCertificate: func(certInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
_, cert, err := this.matchSSL(info.ServerName) _, cert, err := this.matchSSL(certInfo.ServerName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cert == nil { if cert == nil {
return nil, errors.New("no ssl certs found for '" + info.ServerName + "'") return nil, errors.New("no ssl certs found for '" + certInfo.ServerName + "'")
} }
return cert, nil return cert, nil
}, },
@@ -137,7 +109,7 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
} }
if len(sslConfig.Certs) == 0 { if len(sslConfig.Certs) == 0 {
remotelogs.ServerError(server.Id, "BASE_LISTENER", "no ssl certs found for '"+domain+"', server id: "+types.String(server.Id)) remotelogs.ServerError(server.Id, "BASE_LISTENER", "no ssl certs found for '"+domain+"', server id: "+types.String(server.Id), "", nil)
} }
return sslConfig, sslConfig.FirstCert(), nil return sslConfig, sslConfig.FirstCert(), nil

View File

@@ -13,7 +13,7 @@ import (
func TestBaseListener_FindServer(t *testing.T) { func TestBaseListener_FindServer(t *testing.T) {
sharedNodeConfig = &nodeconfigs.NodeConfig{} sharedNodeConfig = &nodeconfigs.NodeConfig{}
var listener = &BaseListener{namedServers: map[string]*NamedServer{}} var listener = &BaseListener{}
listener.Group = &serverconfigs.ServerAddressGroup{} listener.Group = &serverconfigs.ServerAddressGroup{}
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
var server = &serverconfigs.ServerConfig{ var server = &serverconfigs.ServerConfig{
@@ -24,7 +24,7 @@ func TestBaseListener_FindServer(t *testing.T) {
}, },
} }
_ = server.Init() _ = server.Init()
listener.Group.Servers = append(listener.Group.Servers, server) listener.Group.Add(server)
} }
var before = time.Now() var before = time.Now()

View File

@@ -2,8 +2,11 @@ package nodes
import ( import (
"context" "context"
"crypto/tls"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"io" "io"
"log" "log"
@@ -16,7 +19,7 @@ import (
) )
var httpErrorLogger = log.New(io.Discard, "", 0) var httpErrorLogger = log.New(io.Discard, "", 0)
var metricNewConnMap = map[string]bool{} // remoteAddr => bool var metricNewConnMap = map[string]zero.Zero{} // remoteAddr => bool
var metricNewConnMapLocker = &sync.Mutex{} var metricNewConnMapLocker = &sync.Mutex{}
type contextKey struct { type contextKey struct {
@@ -44,9 +47,10 @@ func (this *HTTPListener) Serve() error {
this.httpServer = &http.Server{ this.httpServer = &http.Server{
Addr: this.addr, Addr: this.addr,
Handler: this, Handler: this,
ReadHeaderTimeout: 2 * time.Second, // TODO 改成可以配置 ReadTimeout: 1 * time.Hour, // TODO 改成可以配置
IdleTimeout: 2 * time.Minute, // TODO 改成可以配置 ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
ErrorLog: httpErrorLogger, WriteTimeout: 1 * time.Hour, // TODO 改成可以配置
IdleTimeout: 75 * time.Second, // TODO 改成可以配置
ConnState: func(conn net.Conn, state http.ConnState) { ConnState: func(conn net.Conn, state http.ConnState) {
switch state { switch state {
case http.StateNew: case http.StateNew:
@@ -55,22 +59,36 @@ func (this *HTTPListener) Serve() error {
// 为指标存储连接信息 // 为指标存储连接信息
if sharedNodeConfig.HasHTTPConnectionMetrics() { if sharedNodeConfig.HasHTTPConnectionMetrics() {
metricNewConnMapLocker.Lock() metricNewConnMapLocker.Lock()
metricNewConnMap[conn.RemoteAddr().String()] = true metricNewConnMap[conn.RemoteAddr().String()] = zero.New()
metricNewConnMapLocker.Unlock() metricNewConnMapLocker.Unlock()
} }
case http.StateActive, http.StateIdle, http.StateHijacked:
// Nothing to do
case http.StateClosed: case http.StateClosed:
atomic.AddInt64(&this.countActiveConnections, -1) atomic.AddInt64(&this.countActiveConnections, -1)
// 移除指标存储连接信息 // 移除指标存储连接信息
// 因为中途配置可能有改变,所以暂时不添加条件
metricNewConnMapLocker.Lock() metricNewConnMapLocker.Lock()
delete(metricNewConnMap, conn.RemoteAddr().String()) delete(metricNewConnMap, conn.RemoteAddr().String())
metricNewConnMapLocker.Unlock() metricNewConnMapLocker.Unlock()
} }
}, },
ConnContext: func(ctx context.Context, c net.Conn) context.Context { ConnContext: func(ctx context.Context, conn net.Conn) context.Context {
return context.WithValue(ctx, HTTPConnContextKey, c) tlsConn, ok := conn.(*tls.Conn)
if ok {
conn = NewClientTLSConn(tlsConn)
}
return context.WithValue(ctx, HTTPConnContextKey, conn)
}, },
} }
if !Tea.IsTesting() {
this.httpServer.ErrorLog = httpErrorLogger
}
this.httpServer.SetKeepAlivesEnabled(true) this.httpServer.SetKeepAlivesEnabled(true)
// HTTP协议 // HTTP协议

View File

@@ -1,13 +1,20 @@
package nodes package nodes
import ( import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"net/url" "net/url"
"os/exec"
"regexp" "regexp"
"runtime"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -37,11 +44,11 @@ func NewListenerManager() *ListenerManager {
manager.ticker = time.NewTicker(5 * time.Second) manager.ticker = time.NewTicker(5 * time.Second)
} }
go func() { goman.New(func() {
for range manager.ticker.C { for range manager.ticker.C {
manager.retryListeners() manager.retryListeners()
} }
}() })
return manager return manager
} }
@@ -61,7 +68,7 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
this.lastConfig = node this.lastConfig = node
// 初始化 // 初始化
err := node.Init() err, _ := node.Init()
if err != nil { if err != nil {
return err return err
} }
@@ -113,10 +120,24 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error {
if firstServer == nil { if firstServer == nil {
remotelogs.Error("LISTENER_MANAGER", err.Error()) remotelogs.Error("LISTENER_MANAGER", err.Error())
} else { } else {
remotelogs.ServerError(firstServer.Id, "LISTENER_MANAGER", err.Error()) // 当前占用的进程名
if strings.Contains(err.Error(), "in use") {
portIndex := strings.LastIndex(addr, ":")
if portIndex > 0 {
var port = addr[portIndex+1:]
var processName = this.findProcessNameWithPort(group.IsUDP(), port)
if len(processName) > 0 {
err = errors.New(err.Error() + " (the process using port: '" + processName + "')")
}
}
}
remotelogs.ServerError(firstServer.Id, "LISTENER_MANAGER", "listen '"+addr+"' failed: "+err.Error(), nodeconfigs.NodeLogTypeListenAddressFailed, maps.Map{"address": addr})
} }
continue continue
} else {
// TODO 是否是从错误中恢复
} }
this.listenersMap[addr] = listener this.listenersMap[addr] = listener
} }
@@ -159,7 +180,37 @@ func (this *ListenerManager) retryListeners() {
if err == nil { if err == nil {
delete(this.retryListenerMap, addr) delete(this.retryListenerMap, addr)
this.listenersMap[addr] = listener this.listenersMap[addr] = listener
remotelogs.ServerSuccess(listener.group.FirstServer().Id, "LISTENER_MANAGER", "retry to listen '"+addr+"' successfully") remotelogs.ServerSuccess(listener.group.FirstServer().Id, "LISTENER_MANAGER", "retry to listen '"+addr+"' successfully", nodeconfigs.NodeLogTypeListenAddressFailed, maps.Map{"address": addr})
} }
} }
} }
func (this *ListenerManager) findProcessNameWithPort(isUdp bool, port string) string {
if runtime.GOOS != "linux" {
return ""
}
path, err := exec.LookPath("ss")
if err != nil {
return ""
}
var option = "t"
if isUdp {
option = "u"
}
var cmd = exec.Command(path, "-"+option+"lpn", "sport = :"+port)
var output = &bytes.Buffer{}
cmd.Stdout = output
err = cmd.Run()
if err != nil {
return ""
}
var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(output.String())
if len(matches) > 1 {
return matches[1]
}
return ""
}

View File

@@ -4,8 +4,10 @@ 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/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"net" "net"
"strings" "strings"
@@ -110,10 +112,10 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
} }
// 从源站读取 // 从源站读取
go func() { goman.New(func() {
originBuffer := bytePool32k.Get() originBuffer := utils.BytePool16k.Get()
defer func() { defer func() {
bytePool32k.Put(originBuffer) utils.BytePool16k.Put(originBuffer)
}() }()
for { for {
n, err := originConn.Read(originBuffer) n, err := originConn.Read(originBuffer)
@@ -134,12 +136,12 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
break break
} }
} }
}() })
// 从客户端读取 // 从客户端读取
clientBuffer := bytePool32k.Get() clientBuffer := utils.BytePool16k.Get()
defer func() { defer func() {
bytePool32k.Put(clientBuffer) utils.BytePool16k.Put(clientBuffer)
}() }()
for { for {
n, err := conn.Read(clientBuffer) n, err := conn.Read(clientBuffer)
@@ -177,7 +179,7 @@ func (this *TCPListener) connectOrigin(serverId int64, reverseProxy *serverconfi
} }
conn, err = OriginConnect(origin, remoteAddr) conn, err = OriginConnect(origin, remoteAddr)
if err != nil { if err != nil {
remotelogs.ServerError(serverId, "TCP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error()) remotelogs.ServerError(serverId, "TCP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error(), "", nil)
continue continue
} else { } else {
return return

View File

@@ -3,6 +3,7 @@ package nodes
import ( import (
"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/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
@@ -37,11 +38,11 @@ func (this *UDPListener) Serve() error {
this.connMap = map[string]*UDPConn{} this.connMap = map[string]*UDPConn{}
this.connTicker = utils.NewTicker(1 * time.Minute) this.connTicker = utils.NewTicker(1 * time.Minute)
go func() { goman.New(func() {
for this.connTicker.Next() { for this.connTicker.Next() {
this.gcConns() this.gcConns()
} }
}() })
var buffer = make([]byte, 4*1024) var buffer = make([]byte, 4*1024)
for { for {
@@ -114,7 +115,7 @@ func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfi
} }
conn, err = OriginConnect(origin, remoteAddr.String()) conn, err = OriginConnect(origin, remoteAddr.String())
if err != nil { if err != nil {
remotelogs.ServerError(serverId, "UDP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error()) remotelogs.ServerError(serverId, "UDP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error(), "", nil)
continue continue
} else { } else {
// PROXY Protocol // PROXY Protocol
@@ -188,10 +189,10 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *ne
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId()) stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
} }
go func() { goman.New(func() {
buffer := bytePool32k.Get() buffer := utils.BytePool4k.Get()
defer func() { defer func() {
bytePool32k.Put(buffer) utils.BytePool4k.Put(buffer)
}() }()
for { for {
@@ -214,7 +215,7 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *ne
break break
} }
} }
}() })
return conn return conn
} }

View File

@@ -11,8 +11,10 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/configs" "github.com/TeaOSLab/EdgeNode/internal/configs"
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/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary" "github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/metrics" "github.com/TeaOSLab/EdgeNode/internal/metrics"
"github.com/TeaOSLab/EdgeNode/internal/ratelimit"
"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/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
@@ -24,12 +26,15 @@ import (
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"runtime/debug"
"sort"
"sync" "sync"
"time" "time"
) )
@@ -46,11 +51,16 @@ type Node struct {
isLoaded bool isLoaded bool
sock *gosock.Sock sock *gosock.Sock
locker sync.Mutex locker sync.Mutex
maxCPU int32
maxThreads int
timezone string
} }
func NewNode() *Node { func NewNode() *Node {
return &Node{ return &Node{
sock: gosock.NewTmpSock(teaconst.ProcessName), sock: gosock.NewTmpSock(teaconst.ProcessName),
maxThreads: -1,
} }
} }
@@ -123,7 +133,9 @@ func (this *Node) Start() {
this.startSyncTimer() this.startSyncTimer()
// 状态变更计时器 // 状态变更计时器
go NewNodeStatusExecutor().Listen() goman.New(func() {
NewNodeStatusExecutor().Listen()
})
// 读取配置 // 读取配置
nodeConfig, err := nodeconfigs.SharedNodeConfig() nodeConfig, err := nodeconfigs.SharedNodeConfig()
@@ -132,12 +144,19 @@ func (this *Node) Start() {
return return
} }
teaconst.NodeId = nodeConfig.Id teaconst.NodeId = nodeConfig.Id
err = nodeConfig.Init() teaconst.NodeIdString = types.String(teaconst.NodeId)
err, serverErrors := nodeConfig.Init()
if err != nil { if err != nil {
remotelogs.Error("NODE", "init node config failed: "+err.Error()) remotelogs.Error("NODE", "init node config failed: "+err.Error())
return return
} }
if len(serverErrors) > 0 {
for _, serverErr := range serverErrors {
remotelogs.ServerError(serverErr.Id, "NODE", serverErr.Message, nodeconfigs.NodeLogTypeServerConfigInitFailed, maps.Map{})
}
}
sharedNodeConfig = nodeConfig sharedNodeConfig = nodeConfig
this.onReload(nodeConfig)
// 发送事件 // 发送事件
events.Notify(events.EventLoaded) events.Notify(events.EventLoaded)
@@ -146,13 +165,19 @@ func (this *Node) Start() {
_ = utils.SetRLimit(1024 * 1024) _ = utils.SetRLimit(1024 * 1024)
// 连接API // 连接API
go NewAPIStream().Start() goman.New(func() {
NewAPIStream().Start()
})
// 统计 // 统计
go stats.SharedTrafficStatManager.Start(func() *nodeconfigs.NodeConfig { goman.New(func() {
return sharedNodeConfig stats.SharedTrafficStatManager.Start(func() *nodeconfigs.NodeConfig {
return sharedNodeConfig
})
})
goman.New(func() {
stats.SharedHTTPRequestStatManager.Start()
}) })
go stats.SharedHTTPRequestStatManager.Start()
// 启动端口 // 启动端口
err = sharedListenerManager.Start(nodeConfig) err = sharedListenerManager.Start(nodeConfig)
@@ -290,7 +315,9 @@ func (this *Node) loop() error {
return err return err
} }
case "nodeVersionChanged": case "nodeVersionChanged":
go sharedUpgradeManager.Start() goman.New(func() {
sharedUpgradeManager.Start()
})
} }
} }
@@ -365,6 +392,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
return errors.New("decode config failed: " + err.Error()) return errors.New("decode config failed: " + err.Error())
} }
teaconst.NodeId = nodeConfig.Id teaconst.NodeId = nodeConfig.Id
teaconst.NodeIdString = types.String(teaconst.NodeId)
// 写入到文件中 // 写入到文件中
err = nodeConfig.Save() err = nodeConfig.Save()
@@ -372,16 +400,14 @@ func (this *Node) syncConfig(taskVersion int64) error {
return err return err
} }
err = nodeConfig.Init() err, serverErrors := nodeConfig.Init()
if err != nil { if err != nil {
return err return err
} }
if len(serverErrors) > 0 {
// max cpu for _, serverErr := range serverErrors {
if nodeConfig.MaxCPU > 0 && nodeConfig.MaxCPU < int32(runtime.NumCPU()) { remotelogs.ServerError(serverErr.Id, "NODE", serverErr.Message, nodeconfigs.NodeLogTypeServerConfigInitFailed, maps.Map{})
runtime.GOMAXPROCS(int(nodeConfig.MaxCPU)) }
} else {
runtime.GOMAXPROCS(runtime.NumCPU())
} }
// 刷新配置 // 刷新配置
@@ -403,6 +429,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
sharedWAFManager.UpdatePolicies(nodeConfig.FindAllFirewallPolicies()) sharedWAFManager.UpdatePolicies(nodeConfig.FindAllFirewallPolicies())
iplibrary.SharedActionManager.UpdateActions(nodeConfig.FirewallActions) iplibrary.SharedActionManager.UpdateActions(nodeConfig.FirewallActions)
sharedNodeConfig = nodeConfig sharedNodeConfig = nodeConfig
this.onReload(nodeConfig)
metrics.SharedManager.Update(nodeConfig.MetricItems) metrics.SharedManager.Update(nodeConfig.MetricItems)
@@ -426,7 +453,7 @@ func (this *Node) startSyncTimer() {
remotelogs.Println("NODE", "quit sync timer") remotelogs.Println("NODE", "quit sync timer")
ticker.Stop() ticker.Stop()
}) })
go func() { goman.New(func() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
@@ -449,7 +476,7 @@ func (this *Node) startSyncTimer() {
} }
} }
} }
}() })
} }
// 检查集群设置 // 检查集群设置
@@ -517,7 +544,7 @@ func (this *Node) listenSock() error {
} }
// 启动监听 // 启动监听
go func() { goman.New(func() {
this.sock.OnCommand(func(cmd *gosock.Command) { this.sock.OnCommand(func(cmd *gosock.Command) {
switch cmd.Code { switch cmd.Code {
case "pid": case "pid":
@@ -550,7 +577,7 @@ func (this *Node) listenSock() error {
events.Notify(events.EventQuit) events.Notify(events.EventQuit)
// 监控连接数如果连接数为0则退出进程 // 监控连接数如果连接数为0则退出进程
go func() { goman.New(func() {
for { for {
countActiveConnections := sharedListenerManager.TotalActiveConnections() countActiveConnections := sharedListenerManager.TotalActiveConnections()
if countActiveConnections <= 0 { if countActiveConnections <= 0 {
@@ -559,13 +586,55 @@ func (this *Node) listenSock() error {
} }
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
}() })
case "trackers": case "trackers":
_ = cmd.Reply(&gosock.Command{ _ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{ Params: map[string]interface{}{
"labels": trackers.SharedManager.Labels(), "labels": trackers.SharedManager.Labels(),
}, },
}) })
case "goman":
var posMap = map[string]maps.Map{} // file#line => Map
for _, instance := range goman.List() {
var pos = instance.File + "#" + types.String(instance.Line)
m, ok := posMap[pos]
if ok {
m["count"] = m["count"].(int) + 1
} else {
m = maps.Map{
"pos": pos,
"count": 1,
}
posMap[pos] = m
}
}
var result = []maps.Map{}
for _, m := range posMap {
result = append(result, m)
}
sort.Slice(result, func(i, j int) bool {
return result[i]["count"].(int) > result[j]["count"].(int)
})
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{
"total": runtime.NumGoroutine(),
"result": result,
},
})
case "conns":
ipConns, serverConns := sharedClientConnLimiter.Conns()
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{
"ipConns": ipConns,
"serverConns": serverConns,
"total": sharedListenerManager.TotalActiveConnections(),
"limiter": sharedConnectionsLimiter.Len(),
},
})
} }
}) })
@@ -573,7 +642,7 @@ func (this *Node) listenSock() error {
if err != nil { if err != nil {
logs.Println("NODE", err.Error()) logs.Println("NODE", err.Error())
} }
}() })
events.On(events.EventQuit, func() { events.On(events.EventQuit, func() {
logs.Println("NODE", "quit unix sock") logs.Println("NODE", "quit unix sock")
@@ -582,3 +651,60 @@ func (this *Node) listenSock() error {
return nil return nil
} }
// 重载配置调用
func (this *Node) onReload(config *nodeconfigs.NodeConfig) {
// max cpu
if config.MaxCPU != this.maxCPU {
if config.MaxCPU > 0 && config.MaxCPU < int32(runtime.NumCPU()) {
runtime.GOMAXPROCS(int(config.MaxCPU))
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(config.MaxCPU)+"'")
} else {
runtime.GOMAXPROCS(runtime.NumCPU())
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(runtime.NumCPU())+"'")
}
this.maxCPU = config.MaxCPU
}
// max threads
if config.MaxThreads != this.maxThreads {
if config.MaxThreads > 0 {
debug.SetMaxThreads(config.MaxThreads)
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(config.MaxThreads)+"'")
} else {
debug.SetMaxThreads(nodeconfigs.DefaultMaxThreads)
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(nodeconfigs.DefaultMaxThreads)+"'")
}
this.maxThreads = config.MaxThreads
}
// max tcp connections
if config.TCPMaxConnections <= 0 {
config.TCPMaxConnections = nodeconfigs.DefaultTCPMaxConnections
}
if config.TCPMaxConnections != sharedConnectionsLimiter.Count() {
remotelogs.Println("NODE", "[TCP]changed tcp max connections to '"+types.String(config.TCPMaxConnections)+"'")
sharedConnectionsLimiter.Close()
sharedConnectionsLimiter = ratelimit.NewCounter(config.TCPMaxConnections)
}
// timezone
var timeZone = config.TimeZone
if len(timeZone) == 0 {
timeZone = "Asia/Shanghai"
}
if this.timezone != timeZone {
location, err := time.LoadLocation(timeZone)
if err != nil {
remotelogs.Error("NODE", "[TIMEZONE]change time zone failed: "+err.Error())
return
}
remotelogs.Println("NODE", "[TIMEZONE]change time zone to '"+timeZone+"'")
time.Local = location
this.timezone = timeZone
}
}

View File

@@ -5,6 +5,7 @@ package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"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/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
@@ -16,7 +17,9 @@ var SharedOriginStateManager = NewOriginStateManager()
func init() { func init() {
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
go SharedOriginStateManager.Start() goman.New(func() {
SharedOriginStateManager.Start()
})
}) })
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
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/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -96,10 +97,10 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
} }
// 启动Service // 启动Service
go func() { goman.New(func() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
_ = exec.Command(systemctl, "start", teaconst.SystemdServiceName).Start() _ = exec.Command(systemctl, "start", teaconst.SystemdServiceName).Start()
}() })
if output == "enabled" { if output == "enabled" {
// 检查文件路径是否变化 // 检查文件路径是否变化

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/configs" "github.com/TeaOSLab/EdgeNode/internal/configs"
"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/trackers" "github.com/TeaOSLab/EdgeNode/internal/trackers"
@@ -23,7 +24,9 @@ import (
func init() { func init() {
events.On(events.EventStart, func() { events.On(events.EventStart, func() {
task := NewSyncAPINodesTask() task := NewSyncAPINodesTask()
go task.Start() goman.New(func() {
task.Start()
})
}) })
} }
@@ -131,9 +134,9 @@ func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
return return
} }
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second)
defer func() { defer func() {
cancel() cancelFunc()
}() }()
var conn *grpc.ClientConn var conn *grpc.ClientConn
if u.Scheme == "http" { if u.Scheme == "http" {

View File

@@ -1,35 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"time"
)
func init() {
// 管理时区
var lastTimeZone = ""
events.On(events.EventReload, func() {
if sharedNodeConfig != nil {
var timeZone = sharedNodeConfig.TimeZone
if len(timeZone) == 0 {
timeZone = "Asia/Shanghai"
}
if lastTimeZone != timeZone {
location, err := time.LoadLocation(timeZone)
if err != nil {
remotelogs.Error("TIMEZONE", "change time zone failed: "+err.Error())
return
}
remotelogs.Println("TIMEZONE", "change time zone to '"+timeZone+"'")
time.Local = location
lastTimeZone = timeZone
}
}
})
}

View File

@@ -3,6 +3,7 @@ package nodes
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"net" "net"
@@ -42,7 +43,9 @@ func (this *TOAManager) Run(config *nodeconfigs.TOAConfig) error {
if err != nil { if err != nil {
remotelogs.Error("TOA", "quit error: "+err.Error()) remotelogs.Error("TOA", "quit error: "+err.Error())
} }
_ = this.conn.Close() if this.conn != nil {
_ = this.conn.Close()
}
this.conn = nil this.conn = nil
this.pid = 0 this.pid = 0
} }
@@ -65,7 +68,9 @@ func (this *TOAManager) Run(config *nodeconfigs.TOAConfig) error {
} }
this.pid = cmd.Process.Pid this.pid = cmd.Process.Pid
go func() { _ = cmd.Wait() }() goman.New(func() {
_ = cmd.Wait()
})
return nil return nil
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
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/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"
@@ -60,12 +61,12 @@ func (this *UpgradeManager) Start() {
remotelogs.Println("UPGRADE_MANAGER", "upgrade successfully") remotelogs.Println("UPGRADE_MANAGER", "upgrade successfully")
go func() { goman.New(func() {
err = this.restart() err = this.restart()
if err != nil { if err != nil {
logs.Println("UPGRADE_MANAGER", err.Error()) logs.Println("UPGRADE_MANAGER", err.Error())
} }
}() })
} }
func (this *UpgradeManager) install() error { func (this *UpgradeManager) install() error {

View File

@@ -88,6 +88,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
Name: set.Name, Name: set.Name,
Description: set.Description, Description: set.Description,
Connector: set.Connector, Connector: set.Connector,
IgnoreLocal: set.IgnoreLocal,
} }
for _, a := range set.Actions { for _, a := range set.Actions {
s.AddAction(a.Code, a.Options) s.AddAction(a.Code, a.Options)
@@ -143,6 +144,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
Name: set.Name, Name: set.Name,
Description: set.Description, Description: set.Description,
Connector: set.Connector, Connector: set.Connector,
IgnoreLocal: set.IgnoreLocal,
} }
for _, a := range set.Actions { for _, a := range set.Actions {

View File

@@ -0,0 +1,55 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ratelimit
import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"sync"
)
type Counter struct {
count int
sem chan zero.Zero
done chan zero.Zero
closeOnce sync.Once
}
func NewCounter(count int) *Counter {
return &Counter{
count: count,
sem: make(chan zero.Zero, count),
done: make(chan zero.Zero),
}
}
func (this *Counter) Count() int {
return this.count
}
// Len 已占用数量
func (this *Counter) Len() int {
return len(this.sem)
}
func (this *Counter) Ack() bool {
select {
case this.sem <- zero.New():
return true
case <-this.done:
return false
}
}
func (this *Counter) Release() {
select {
case <-this.sem:
default:
// 总是能Release成功
}
}
func (this *Counter) Close() {
this.closeOnce.Do(func() {
close(this.done)
})
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ratelimit
import (
"testing"
"time"
)
func TestCounter_ACK(t *testing.T) {
var counter = NewCounter(10)
go func() {
for i := 0; i < 10; i++ {
counter.Ack()
}
//counter.Release()
t.Log("waiting", time.Now().Unix())
counter.Ack()
t.Log("done", time.Now().Unix())
}()
time.Sleep(1 * time.Second)
counter.Close()
time.Sleep(1 * time.Second)
}
func TestCounter_Release(t *testing.T) {
var counter = NewCounter(10)
for i := 0; i < 10; i++ {
counter.Ack()
}
for i := 0; i < 10; i++ {
counter.Release()
}
t.Log(len(counter.sem))
}

View File

@@ -1,14 +1,19 @@
package remotelogs package remotelogs
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"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/cespare/xxhash" "github.com/cespare/xxhash"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"strings"
"time" "time"
) )
@@ -17,7 +22,10 @@ var logChan = make(chan *pb.NodeLog, 1024)
func init() { func init() {
// 定期上传日志 // 定期上传日志
ticker := time.NewTicker(60 * time.Second) ticker := time.NewTicker(60 * time.Second)
go func() { if Tea.IsTesting() {
ticker = time.NewTicker(10 * time.Second)
}
goman.New(func() {
for range ticker.C { for range ticker.C {
var tr = trackers.Begin("UPLOAD_REMOTE_LOGS") var tr = trackers.Begin("UPLOAD_REMOTE_LOGS")
err := uploadLogs() err := uploadLogs()
@@ -26,25 +34,20 @@ func init() {
logs.Println("[LOG]" + err.Error()) logs.Println("[LOG]" + err.Error())
} }
} }
}() })
} }
// Println 打印普通信息 // Println 打印普通信息
func Println(tag string, description string) { func Println(tag string, description string) {
logs.Println("[" + tag + "]" + description) logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig == nil {
return
}
select { select {
case logChan <- &pb.NodeLog{ case logChan <- &pb.NodeLog{
Role: teaconst.Role, Role: teaconst.Role,
Tag: tag, Tag: tag,
Description: description, Description: description,
Level: "info", Level: "info",
NodeId: nodeConfig.Id, NodeId: teaconst.NodeId,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
}: }:
default: default:
@@ -56,18 +59,13 @@ func Println(tag string, description string) {
func Warn(tag string, description string) { func Warn(tag string, description string) {
logs.Println("[" + tag + "]" + description) logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig == nil {
return
}
select { select {
case logChan <- &pb.NodeLog{ case logChan <- &pb.NodeLog{
Role: teaconst.Role, Role: teaconst.Role,
Tag: tag, Tag: tag,
Description: description, Description: description,
Level: "warning", Level: "warning",
NodeId: nodeConfig.Id, NodeId: teaconst.NodeId,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
}: }:
default: default:
@@ -79,9 +77,10 @@ func Warn(tag string, description string) {
func Error(tag string, description string) { func Error(tag string, description string) {
logs.Println("[" + tag + "]" + description) logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig() // 忽略RPC连接错误
if nodeConfig == nil { var level = "error"
return if strings.Contains(description, "code = Unavailable desc") {
level = "warning"
} }
select { select {
@@ -89,8 +88,8 @@ func Error(tag string, description string) {
Role: teaconst.Role, Role: teaconst.Role,
Tag: tag, Tag: tag,
Description: description, Description: description,
Level: "error", Level: level,
NodeId: nodeConfig.Id, NodeId: teaconst.NodeId,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
}: }:
default: default:
@@ -111,12 +110,18 @@ func ErrorObject(tag string, err error) {
} }
// ServerError 打印服务相关错误信息 // ServerError 打印服务相关错误信息
func ServerError(serverId int64, tag string, description string) { func ServerError(serverId int64, tag string, description string, logType nodeconfigs.NodeLogType, params maps.Map) {
logs.Println("[" + tag + "]" + description) logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig() // 参数
if nodeConfig == nil { var paramsJSON []byte
return if len(params) > 0 {
p, err := json.Marshal(params)
if err != nil {
logs.Println("[LOG]" + err.Error())
} else {
paramsJSON = p
}
} }
select { select {
@@ -125,9 +130,11 @@ func ServerError(serverId int64, tag string, description string) {
Tag: tag, Tag: tag,
Description: description, Description: description,
Level: "error", Level: "error",
NodeId: nodeConfig.Id, NodeId: teaconst.NodeId,
ServerId: serverId, ServerId: serverId,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
Type: logType,
ParamsJSON: paramsJSON,
}: }:
default: default:
@@ -135,12 +142,18 @@ func ServerError(serverId int64, tag string, description string) {
} }
// ServerSuccess 打印服务相关成功信息 // ServerSuccess 打印服务相关成功信息
func ServerSuccess(serverId int64, tag string, description string) { func ServerSuccess(serverId int64, tag string, description string, logType nodeconfigs.NodeLogType, params maps.Map) {
logs.Println("[" + tag + "]" + description) logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig() // 参数
if nodeConfig == nil { var paramsJSON []byte
return if len(params) > 0 {
p, err := json.Marshal(params)
if err != nil {
logs.Println("[LOG]" + err.Error())
} else {
paramsJSON = p
}
} }
select { select {
@@ -149,9 +162,11 @@ func ServerSuccess(serverId int64, tag string, description string) {
Tag: tag, Tag: tag,
Description: description, Description: description,
Level: "success", Level: "success",
NodeId: nodeConfig.Id, NodeId: teaconst.NodeId,
ServerId: serverId, ServerId: serverId,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
Type: logType,
ParamsJSON: paramsJSON,
}: }:
default: default:

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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/iplibrary" "github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/monitor" "github.com/TeaOSLab/EdgeNode/internal/monitor"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
@@ -20,6 +21,13 @@ import (
"time" "time"
) )
type StatItem struct {
Bytes int64
CountRequests int64
CountAttackRequests int64
AttackBytes int64
}
var SharedHTTPRequestStatManager = NewHTTPRequestStatManager() var SharedHTTPRequestStatManager = NewHTTPRequestStatManager()
// HTTPRequestStatManager HTTP请求相关的统计 // HTTPRequestStatManager HTTP请求相关的统计
@@ -29,10 +37,10 @@ type HTTPRequestStatManager struct {
userAgentChan chan string userAgentChan chan string
firewallRuleGroupChan chan string firewallRuleGroupChan chan string
cityMap map[string]int64 // serverId@country@province@city => count 不需要加锁因为我们是使用channel依次执行的 cityMap map[string]*StatItem // serverId@country@province@city => *StatItem 不需要加锁因为我们是使用channel依次执行的
providerMap map[string]int64 // serverId@provider => count providerMap map[string]int64 // serverId@provider => count
systemMap map[string]int64 // serverId@system@version => count systemMap map[string]int64 // serverId@system@version => count
browserMap map[string]int64 // serverId@browser@version => count browserMap map[string]int64 // serverId@browser@version => count
dailyFirewallRuleGroupMap map[string]int64 // serverId@firewallRuleGroupId@action => count dailyFirewallRuleGroupMap map[string]int64 // serverId@firewallRuleGroupId@action => count
@@ -45,7 +53,7 @@ func NewHTTPRequestStatManager() *HTTPRequestStatManager {
ipChan: make(chan string, 10_000), // TODO 将来可以配置容量 ipChan: make(chan string, 10_000), // TODO 将来可以配置容量
userAgentChan: make(chan string, 10_000), // TODO 将来可以配置容量 userAgentChan: make(chan string, 10_000), // TODO 将来可以配置容量
firewallRuleGroupChan: make(chan string, 10_000), // TODO 将来可以配置容量 firewallRuleGroupChan: make(chan string, 10_000), // TODO 将来可以配置容量
cityMap: map[string]int64{}, cityMap: map[string]*StatItem{},
providerMap: map[string]int64{}, providerMap: map[string]int64{},
systemMap: map[string]int64{}, systemMap: map[string]int64{},
browserMap: map[string]int64{}, browserMap: map[string]int64{},
@@ -56,17 +64,17 @@ func NewHTTPRequestStatManager() *HTTPRequestStatManager {
// Start 启动 // Start 启动
func (this *HTTPRequestStatManager) Start() { func (this *HTTPRequestStatManager) Start() {
// 上传请求总数 // 上传请求总数
go func() { goman.New(func() {
ticker := time.NewTicker(1 * time.Minute) ticker := time.NewTicker(1 * time.Minute)
go func() { goman.New(func() {
for range ticker.C { for range ticker.C {
if this.totalAttackRequests > 0 { if this.totalAttackRequests > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAttackRequests, maps.Map{"total": this.totalAttackRequests}) monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAttackRequests, maps.Map{"total": this.totalAttackRequests})
this.totalAttackRequests = 0 this.totalAttackRequests = 0
} }
} }
}() })
}() })
loopTicker := time.NewTicker(1 * time.Second) loopTicker := time.NewTicker(1 * time.Second)
uploadTicker := time.NewTicker(30 * time.Minute) uploadTicker := time.NewTicker(30 * time.Minute)
@@ -107,7 +115,7 @@ func (this *HTTPRequestStatManager) Start() {
} }
// AddRemoteAddr 添加客户端地址 // AddRemoteAddr 添加客户端地址
func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr string) { func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr string, bytes int64, isAttack bool) {
if len(remoteAddr) == 0 { if len(remoteAddr) == 0 {
return return
} }
@@ -122,8 +130,14 @@ func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr str
ip = remoteAddr[:index] ip = remoteAddr[:index]
} }
if len(ip) > 0 { if len(ip) > 0 {
var s string
if isAttack {
s = strconv.FormatInt(serverId, 10) + "@" + ip + "@" + types.String(bytes) + "@1"
} else {
s = strconv.FormatInt(serverId, 10) + "@" + ip + "@" + types.String(bytes) + "@0"
}
select { select {
case this.ipChan <- strconv.FormatInt(serverId, 10) + "@" + ip: case this.ipChan <- s:
default: default:
// 超出容量我们就丢弃 // 超出容量我们就丢弃
} }
@@ -168,16 +182,33 @@ Loop:
for { for {
select { select {
case ipString := <-this.ipChan: case ipString := <-this.ipChan:
atIndex := strings.Index(ipString, "@") // serverId@ip@bytes@isAttack
if atIndex < 0 { pieces := strings.Split(ipString, "@")
if len(pieces) < 4 {
continue continue
} }
serverId := ipString[:atIndex] serverId := pieces[0]
ip := ipString[atIndex+1:] ip := pieces[1]
if iplibrary.SharedLibrary != nil { if iplibrary.SharedLibrary != nil {
result, err := iplibrary.SharedLibrary.Lookup(ip) result, err := iplibrary.SharedLibrary.Lookup(ip)
if err == nil && result != nil { if err == nil && result != nil {
this.cityMap[serverId+"@"+result.Country+"@"+result.Province+"@"+result.City]++ if len(result.Country) == 0 {
continue
}
var key = serverId + "@" + result.Country + "@" + result.Province + "@" + result.City
stat, ok := this.cityMap[key]
if !ok {
stat = &StatItem{}
this.cityMap[key] = stat
}
stat.Bytes += types.Int64(pieces[2])
stat.CountRequests++
if types.Int8(pieces[3]) == 1 {
stat.AttackBytes += types.Int64(pieces[2])
stat.CountAttackRequests++
}
if len(result.ISP) > 0 { if len(result.ISP) > 0 {
this.providerMap[serverId+"@"+result.ISP]++ this.providerMap[serverId+"@"+result.ISP]++
@@ -237,14 +268,17 @@ func (this *HTTPRequestStatManager) Upload() error {
pbProviders := []*pb.UploadServerHTTPRequestStatRequest_RegionProvider{} pbProviders := []*pb.UploadServerHTTPRequestStatRequest_RegionProvider{}
pbSystems := []*pb.UploadServerHTTPRequestStatRequest_System{} pbSystems := []*pb.UploadServerHTTPRequestStatRequest_System{}
pbBrowsers := []*pb.UploadServerHTTPRequestStatRequest_Browser{} pbBrowsers := []*pb.UploadServerHTTPRequestStatRequest_Browser{}
for k, count := range this.cityMap { for k, stat := range this.cityMap {
pieces := strings.SplitN(k, "@", 4) pieces := strings.SplitN(k, "@", 4)
pbCities = append(pbCities, &pb.UploadServerHTTPRequestStatRequest_RegionCity{ pbCities = append(pbCities, &pb.UploadServerHTTPRequestStatRequest_RegionCity{
ServerId: types.Int64(pieces[0]), ServerId: types.Int64(pieces[0]),
CountryName: pieces[1], CountryName: pieces[1],
ProvinceName: pieces[2], ProvinceName: pieces[2],
CityName: pieces[3], CityName: pieces[3],
Count: count, CountRequests: stat.CountRequests,
CountAttackRequests: stat.CountAttackRequests,
Bytes: stat.Bytes,
AttackBytes: stat.AttackBytes,
}) })
} }
for k, count := range this.providerMap { for k, count := range this.providerMap {
@@ -300,7 +334,7 @@ func (this *HTTPRequestStatManager) Upload() error {
} }
// 重置数据 // 重置数据
this.cityMap = map[string]int64{} this.cityMap = map[string]*StatItem{}
this.providerMap = map[string]int64{} this.providerMap = map[string]int64{}
this.systemMap = map[string]int64{} this.systemMap = map[string]int64{}
this.browserMap = map[string]int64{} this.browserMap = map[string]int64{}

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"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/monitor" "github.com/TeaOSLab/EdgeNode/internal/monitor"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
@@ -55,17 +56,17 @@ func (this *TrafficStatManager) Start(configFunc func() *nodeconfigs.NodeConfig)
this.configFunc = configFunc this.configFunc = configFunc
// 上传请求总数 // 上传请求总数
go func() { goman.New(func() {
ticker := time.NewTicker(1 * time.Minute) ticker := time.NewTicker(1 * time.Minute)
go func() { goman.New(func() {
for range ticker.C { for range ticker.C {
if this.totalRequests > 0 { if this.totalRequests > 0 {
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemRequests, maps.Map{"total": this.totalRequests}) monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemRequests, maps.Map{"total": this.totalRequests})
this.totalRequests = 0 this.totalRequests = 0
} }
} }
}() })
}() })
// 上传统计数据 // 上传统计数据
duration := 5 * time.Minute duration := 5 * time.Minute

View File

@@ -1,15 +1,14 @@
package ttlcache package ttlcache
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
"time" "time"
) )
// Cache TTL缓存 // Cache TTL缓存
// 最大的缓存时间为30 * 86400 // 最大的缓存时间为30 * 86400
// Piece数据结构 // Piece数据结构
// Piece1 | Piece2 | Piece3 | ... // Piece1 | Piece2 | Piece3 | ...
// [ Item1, Item2, ... | ... // [ Item1, Item2, ... ] | ...
// KeyMap列表数据结构 // KeyMap列表数据结构
// { timestamp1 => [key1, key2, ...] }, ... // { timestamp1 => [key1, key2, ...] }, ...
type Cache struct { type Cache struct {
@@ -19,14 +18,13 @@ type Cache struct {
maxItems int maxItems int
gcPieceIndex int gcPieceIndex int
ticker *utils.Ticker
} }
func NewCache(opt ...OptionInterface) *Cache { func NewCache(opt ...OptionInterface) *Cache {
countPieces := 128 countPieces := 128
maxItems := 2_000_000 maxItems := 2_000_000
var delta = systemMemoryGB() / 4 var delta = systemMemoryGB() / 8
if delta > 0 { if delta > 0 {
maxItems *= delta maxItems *= delta
} }
@@ -56,13 +54,8 @@ func NewCache(opt ...OptionInterface) *Cache {
cache.pieces = append(cache.pieces, NewPiece(maxItems/countPieces)) cache.pieces = append(cache.pieces, NewPiece(maxItems/countPieces))
} }
// start timer // Add to manager
go func() { SharedManager.Add(cache)
cache.ticker = utils.NewTicker(5 * time.Second)
for cache.ticker.Next() {
cache.GC()
}
}()
return cache return cache
} }
@@ -149,12 +142,10 @@ func (this *Cache) Clean() {
} }
func (this *Cache) Destroy() { func (this *Cache) Destroy() {
SharedManager.Remove(this)
this.isDestroyed = true this.isDestroyed = true
if this.ticker != nil {
this.ticker.Stop()
this.ticker = nil
}
for _, piece := range this.pieces { for _, piece := range this.pieces {
piece.Destroy() piece.Destroy()
} }

View File

@@ -123,13 +123,18 @@ func TestCache_GC(t *testing.T) {
func TestCache_GC2(t *testing.T) { func TestCache_GC2(t *testing.T) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
cache := NewCache() cache1 := NewCache(NewPiecesOption(32))
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(rands.Int(0, 100))) cache1.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(rands.Int(0, 10)))
}
cache2 := NewCache(NewPiecesOption(5))
for i := 0; i < 1_000_000; i++ {
cache2.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(rands.Int(0, 10)))
} }
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
t.Log(cache.Count(), "items") t.Log(cache1.Count(), "items", cache2.Count(), "items")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }

View File

@@ -0,0 +1,54 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ttlcache
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"sync"
"time"
)
var SharedManager = NewManager()
type Manager struct {
ticker *time.Ticker
locker sync.Mutex
cacheMap map[*Cache]zero.Zero
}
func NewManager() *Manager {
var manager = &Manager{
ticker: time.NewTicker(3 * time.Second),
cacheMap: map[*Cache]zero.Zero{},
}
goman.New(func() {
manager.init()
})
return manager
}
func (this *Manager) init() {
for range this.ticker.C {
this.locker.Lock()
for cache := range this.cacheMap {
cache.GC()
}
this.locker.Unlock()
}
}
func (this *Manager) Add(cache *Cache) {
this.locker.Lock()
this.cacheMap[cache] = zero.New()
this.locker.Unlock()
}
func (this *Manager) Remove(cache *Cache) {
this.locker.Lock()
delete(this.cacheMap, cache)
this.locker.Unlock()
}

View File

@@ -1,16 +1,25 @@
package utils package utils
var BytePool1024 = NewBytePool(20480, 1024) import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/iwind/TeaGo/Tea"
"time"
)
// pool for get byte slice var BytePool1k = NewBytePool(20480, 1024)
var BytePool4k = NewBytePool(20480, 4*1024)
var BytePool16k = NewBytePool(40960, 16*1024)
var BytePool32k = NewBytePool(20480, 32*1024)
// BytePool pool for get byte slice
type BytePool struct { type BytePool struct {
c chan []byte c chan []byte
length int maxSize int
length int
lastSize int hasNew bool
} }
// 创建新对象 // NewBytePool 创建新对象
func NewBytePool(maxSize, length int) *BytePool { func NewBytePool(maxSize, length int) *BytePool {
if maxSize <= 0 { if maxSize <= 0 {
maxSize = 1024 maxSize = 1024
@@ -18,24 +27,47 @@ func NewBytePool(maxSize, length int) *BytePool {
if length <= 0 { if length <= 0 {
length = 128 length = 128
} }
pool := &BytePool{ var pool = &BytePool{
c: make(chan []byte, maxSize), c: make(chan []byte, maxSize),
length: length, maxSize: maxSize,
length: length,
} }
pool.init()
return pool return pool
} }
// 获取一个新的byte slice // 初始化
func (this *BytePool) init() {
var ticker = time.NewTicker(2 * time.Minute)
if Tea.IsTesting() {
ticker = time.NewTicker(5 * time.Second)
}
goman.New(func() {
for range ticker.C {
if this.hasNew {
this.hasNew = false
continue
}
this.Purge()
}
})
}
// Get 获取一个新的byte slice
func (this *BytePool) Get() (b []byte) { func (this *BytePool) Get() (b []byte) {
select { select {
case b = <-this.c: case b = <-this.c:
default: default:
b = make([]byte, this.length) b = make([]byte, this.length)
this.hasNew = true
} }
return return
} }
// 放回一个使用过的byte slice // Put 放回一个使用过的byte slice
func (this *BytePool) Put(b []byte) { func (this *BytePool) Put(b []byte) {
if cap(b) != this.length { if cap(b) != this.length {
return return
@@ -47,7 +79,30 @@ func (this *BytePool) Put(b []byte) {
} }
} }
// 当前的数量 // Length 单个字节slice长度
func (this *BytePool) Length() int {
return this.length
}
// Size 当前的数量
func (this *BytePool) Size() int { func (this *BytePool) Size() int {
return len(this.c) return len(this.c)
} }
// Purge 清理
func (this *BytePool) Purge() {
// 1%
var count = len(this.c) / 100
if count == 0 {
return
}
Loop:
for i := 0; i < count; i++ {
select {
case <-this.c:
default:
break Loop
}
}
}

View File

@@ -27,6 +27,26 @@ func TestNewBytePool(t *testing.T) {
a.IsTrue(len(pool.c) == 5) a.IsTrue(len(pool.c) == 5)
} }
func TestBytePool_Memory(t *testing.T) {
var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1)
var pool = NewBytePool(20480, 32*1024)
for i := 0; i < 20480; i++ {
pool.Put(make([]byte, 32*1024))
}
//pool.Purge()
//time.Sleep(60 * time.Second)
runtime.GC()
var stat2 = &runtime.MemStats{}
runtime.ReadMemStats(stat2)
t.Log((stat2.HeapInuse-stat1.HeapInuse)/1024/1024, "MB,", pool.Size(), "slices")
}
func BenchmarkBytePool_Get(b *testing.B) { func BenchmarkBytePool_Get(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package expires
import "sync"
type IdKeyMap struct {
idKeys map[int64]string // id => key
keyIds map[string]int64 // key => id
locker sync.Mutex
}
func NewIdKeyMap() *IdKeyMap {
return &IdKeyMap{
idKeys: map[int64]string{},
keyIds: map[string]int64{},
}
}
func (this *IdKeyMap) Add(id int64, key string) {
oldKey, ok := this.idKeys[id]
if ok {
delete(this.keyIds, oldKey)
}
oldId, ok := this.keyIds[key]
if ok {
delete(this.idKeys, oldId)
}
this.idKeys[id] = key
this.keyIds[key] = id
}
func (this *IdKeyMap) Key(id int64) (key string, ok bool) {
key, ok = this.idKeys[id]
return
}
func (this *IdKeyMap) Id(key string) (id int64, ok bool) {
id, ok = this.keyIds[key]
return
}
func (this *IdKeyMap) DeleteId(id int64) {
key, ok := this.idKeys[id]
if ok {
delete(this.keyIds, key)
}
delete(this.idKeys, id)
}
func (this *IdKeyMap) DeleteKey(key string) {
id, ok := this.keyIds[key]
if ok {
delete(this.idKeys, id)
}
delete(this.keyIds, key)
}
func (this *IdKeyMap) Len() int {
return len(this.idKeys)
}

View File

@@ -0,0 +1,46 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package expires
import (
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestNewIdKeyMap(t *testing.T) {
var a = assert.NewAssertion(t)
var m = NewIdKeyMap()
m.Add(1, "1")
m.Add(1, "2")
m.Add(100, "100")
logs.PrintAsJSON(m.idKeys, t)
logs.PrintAsJSON(m.keyIds, t)
{
k, ok := m.Key(1)
a.IsTrue(ok)
a.IsTrue(k == "2")
}
{
_, ok := m.Key(2)
a.IsFalse(ok)
}
m.DeleteKey("2")
{
_, ok := m.Key(1)
a.IsFalse(ok)
}
logs.PrintAsJSON(m.idKeys, t)
logs.PrintAsJSON(m.keyIds, t)
m.DeleteId(100)
logs.PrintAsJSON(m.idKeys, t)
logs.PrintAsJSON(m.keyIds, t)
}

View File

@@ -1,43 +1,53 @@
package expires package expires
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"sync" "sync"
"time"
) )
type ItemMap = map[int64]bool type ItemMap = map[int64]zero.Zero
type List struct { type List struct {
expireMap map[int64]ItemMap // expires timestamp => map[id]bool expireMap map[int64]ItemMap // expires timestamp => map[id]ItemMap
itemsMap map[int64]int64 // itemId => timestamp itemsMap map[int64]int64 // itemId => timestamp
locker sync.Mutex locker sync.Mutex
ticker *time.Ticker
gcCallback func(itemId int64)
} }
func NewList() *List { func NewList() *List {
return &List{ var list = &List{
expireMap: map[int64]ItemMap{}, expireMap: map[int64]ItemMap{},
itemsMap: map[int64]int64{}, itemsMap: map[int64]int64{},
} }
SharedManager.Add(list)
return list
} }
// Add 添加条目
// 如果条目已经存在,则覆盖
func (this *List) Add(itemId int64, expiresAt int64) { func (this *List) Add(itemId int64, expiresAt int64) {
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
// 是否已经存在 // 是否已经存在
_, ok := this.itemsMap[itemId] oldExpiresAt, ok := this.itemsMap[itemId]
if ok { if ok {
this.removeItem(itemId) if oldExpiresAt == expiresAt {
return
}
delete(this.expireMap, oldExpiresAt)
} }
expireItemMap, ok := this.expireMap[expiresAt] expireItemMap, ok := this.expireMap[expiresAt]
if ok { if ok {
expireItemMap[itemId] = true expireItemMap[itemId] = zero.New()
} else { } else {
expireItemMap = ItemMap{ expireItemMap = ItemMap{
itemId: true, itemId: zero.New(),
} }
this.expireMap[expiresAt] = expireItemMap this.expireMap[expiresAt] = expireItemMap
} }
@@ -56,33 +66,16 @@ func (this *List) GC(timestamp int64, callback func(itemId int64)) {
itemMap := this.gcItems(timestamp) itemMap := this.gcItems(timestamp)
this.locker.Unlock() this.locker.Unlock()
for itemId := range itemMap { if callback != nil {
callback(itemId) for itemId := range itemMap {
callback(itemId)
}
} }
} }
func (this *List) StartGC(callback func(itemId int64)) { func (this *List) OnGC(callback func(itemId int64)) *List {
this.ticker = time.NewTicker(1 * time.Second) this.gcCallback = callback
lastTimestamp := int64(0) return this
for range this.ticker.C {
timestamp := time.Now().Unix()
if lastTimestamp == 0 {
lastTimestamp = timestamp - 3600
}
if timestamp >= lastTimestamp {
for i := lastTimestamp; i <= timestamp; i++ {
this.GC(i, callback)
}
} else {
for i := timestamp; i <= lastTimestamp; i++ {
this.GC(i, callback)
}
}
// 这样做是为了防止系统时钟突变
lastTimestamp = timestamp
}
} }
func (this *List) removeItem(itemId int64) { func (this *List) removeItem(itemId int64) {

View File

@@ -1,9 +1,11 @@
package expires package expires
import ( import (
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"math" "math"
"runtime"
"testing" "testing"
"time" "time"
) )
@@ -24,12 +26,19 @@ func TestList_Add(t *testing.T) {
} }
func TestList_Add_Overwrite(t *testing.T) { func TestList_Add_Overwrite(t *testing.T) {
var timestamp = time.Now().Unix()
list := NewList() list := NewList()
list.Add(1, time.Now().Unix()+1) list.Add(1, timestamp+1)
list.Add(1, time.Now().Unix()+1) list.Add(1, timestamp+1)
list.Add(1, time.Now().Unix()+2) list.Add(1, timestamp+2)
logs.PrintAsJSON(list.expireMap, t) logs.PrintAsJSON(list.expireMap, t)
logs.PrintAsJSON(list.itemsMap, t) logs.PrintAsJSON(list.itemsMap, t)
var a = assert.NewAssertion(t)
a.IsTrue(len(list.itemsMap) == 1)
a.IsTrue(len(list.expireMap) == 1)
a.IsTrue(list.itemsMap[1] == timestamp+2)
} }
func TestList_Remove(t *testing.T) { func TestList_Remove(t *testing.T) {
@@ -63,11 +72,13 @@ func TestList_Start_GC(t *testing.T) {
list.Add(7, time.Now().Unix()+6) list.Add(7, time.Now().Unix()+6)
list.Add(8, time.Now().Unix()+6) list.Add(8, time.Now().Unix()+6)
list.OnGC(func(itemId int64) {
t.Log("gc:", itemId, timeutil.Format("H:i:s"))
time.Sleep(2 * time.Second)
})
go func() { go func() {
list.StartGC(func(itemId int64) { SharedManager.Add(list)
t.Log("gc:", itemId, timeutil.Format("H:i:s"))
time.Sleep(2 * time.Second)
})
}() }()
time.Sleep(20 * time.Second) time.Sleep(20 * time.Second)
@@ -75,7 +86,10 @@ func TestList_Start_GC(t *testing.T) {
func TestList_ManyItems(t *testing.T) { func TestList_ManyItems(t *testing.T) {
list := NewList() list := NewList()
for i := 0; i < 100_000; i++ { for i := 0; i < 1_000; i++ {
list.Add(int64(i), time.Now().Unix())
}
for i := 0; i < 1_000; i++ {
list.Add(int64(i), time.Now().Unix()+1) list.Add(int64(i), time.Now().Unix()+1)
} }
@@ -85,35 +99,69 @@ func TestList_ManyItems(t *testing.T) {
count++ count++
}) })
t.Log("gc", count, "items") t.Log("gc", count, "items")
t.Log(time.Since(now).Seconds()*1000, "ms") t.Log(time.Now().Sub(now))
} }
func TestList_Map_Performance(t *testing.T) { func TestList_Map_Performance(t *testing.T) {
t.Log("max uint32", math.MaxUint32) t.Log("max uint32", math.MaxUint32)
var timestamp = time.Now().Unix()
{ {
m := map[int64]int64{} m := map[int64]int64{}
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
m[int64(i)] = time.Now().Unix() m[int64(i)] = timestamp
} }
now := time.Now() now := time.Now()
for i := 0; i < 100_000; i++ { for i := 0; i < 100_000; i++ {
delete(m, int64(i)) delete(m, int64(i))
} }
t.Log(time.Since(now).Seconds()*1000, "ms") t.Log(time.Now().Sub(now))
}
{
m := map[uint64]int64{}
for i := 0; i < 1_000_000; i++ {
m[uint64(i)] = timestamp
}
now := time.Now()
for i := 0; i < 100_000; i++ {
delete(m, uint64(i))
}
t.Log(time.Now().Sub(now))
} }
{ {
m := map[uint32]int64{} m := map[uint32]int64{}
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
m[uint32(i)] = time.Now().Unix() m[uint32(i)] = timestamp
} }
now := time.Now() now := time.Now()
for i := 0; i < 100_000; i++ { for i := 0; i < 100_000; i++ {
delete(m, uint32(i)) delete(m, uint32(i))
} }
t.Log(time.Since(now).Seconds()*1000, "ms") t.Log(time.Now().Sub(now))
}
}
func Benchmark_Map_Uint64(b *testing.B) {
runtime.GOMAXPROCS(1)
var timestamp = uint64(time.Now().Unix())
var i uint64
var count uint64 = 1_000_000
m := map[uint64]uint64{}
for i = 0; i < count; i++ {
m[i] = timestamp
}
for n := 0; n < b.N; n++ {
for i = 0; i < count; i++ {
_ = m[i]
}
} }
} }

View File

@@ -0,0 +1,72 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package expires
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"sync"
"time"
)
var SharedManager = NewManager()
type Manager struct {
listMap map[*List]zero.Zero
locker sync.Mutex
ticker *time.Ticker
}
func NewManager() *Manager {
var manager = &Manager{
listMap: map[*List]zero.Zero{},
ticker: time.NewTicker(1 * time.Second),
}
goman.New(func() {
manager.init()
})
return manager
}
func (this *Manager) init() {
var lastTimestamp = int64(0)
for range this.ticker.C {
timestamp := time.Now().Unix()
if lastTimestamp == 0 {
lastTimestamp = timestamp - 3600
}
if timestamp >= lastTimestamp {
for i := lastTimestamp; i <= timestamp; i++ {
this.locker.Lock()
for list := range this.listMap {
list.GC(i, list.gcCallback)
}
this.locker.Unlock()
}
} else {
for i := timestamp; i <= lastTimestamp; i++ {
this.locker.Lock()
for list := range this.listMap {
list.GC(i, list.gcCallback)
}
this.locker.Unlock()
}
}
// 这样做是为了防止系统时钟突变
lastTimestamp = timestamp
}
}
func (this *Manager) Add(list *List) {
this.locker.Lock()
this.listMap[list] = zero.New()
this.locker.Unlock()
}
func (this *Manager) Remove(list *List) {
this.locker.Lock()
delete(this.listMap, list)
this.locker.Unlock()
}

View File

@@ -5,6 +5,7 @@ package utils
import ( import (
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/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -15,7 +16,9 @@ var SharedFreeHoursManager = NewFreeHoursManager()
func init() { func init() {
events.On(events.EventLoaded, func() { events.On(events.EventLoaded, func() {
go SharedFreeHoursManager.Start() goman.New(func() {
SharedFreeHoursManager.Start()
})
}) })
} }

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