Compare commits
329 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16d8f1afa | ||
|
|
1bab7bfcba | ||
|
|
5928875623 | ||
|
|
734cf81ff0 | ||
|
|
de8c2e13f1 | ||
|
|
0742dc963d | ||
|
|
1fdce3ef7e | ||
|
|
2079b0ebee | ||
|
|
c706f2fdf1 | ||
|
|
bd3247668d | ||
|
|
73024fe38c | ||
|
|
db520858b3 | ||
|
|
84c8a351a9 | ||
|
|
c6c0823d30 | ||
|
|
1be64adb6a | ||
|
|
d0610a5001 | ||
|
|
a2f7511a46 | ||
|
|
6e8c886cd6 | ||
|
|
03f2130827 | ||
|
|
9fea0749a0 | ||
|
|
71e0d9ce07 | ||
|
|
24e69028f8 | ||
|
|
34521dbc5c | ||
|
|
2c59ae4a5b | ||
|
|
d0bd7bb88d | ||
|
|
47c24b4aa8 | ||
|
|
4eb58a3082 | ||
|
|
c10c7cc157 | ||
|
|
7fd8d7756b | ||
|
|
032c118f49 | ||
|
|
b6cab3919a | ||
|
|
8edd30bdd8 | ||
|
|
a8c8d80e3b | ||
|
|
c43b6b37ea | ||
|
|
ac2d57d2f1 | ||
|
|
83ac62cda3 | ||
|
|
c0909a2cd0 | ||
|
|
a73b9f2674 | ||
|
|
3e79b71afc | ||
|
|
d3caccbb55 | ||
|
|
f95bac8d38 | ||
|
|
41d2ab728b | ||
|
|
b319061e85 | ||
|
|
99b8686a49 | ||
|
|
fe8c5b505a | ||
|
|
f88d0982ed | ||
|
|
a9389d53e1 | ||
|
|
fc4b45fec7 | ||
|
|
9b22e6cf69 | ||
|
|
7bd7f7da45 | ||
|
|
b68e6517df | ||
|
|
bbae229d08 | ||
|
|
c73a6cbfe8 | ||
|
|
e869e8e4d6 | ||
|
|
bde4e8507f | ||
|
|
5ae25cffa0 | ||
|
|
44b721d0d3 | ||
|
|
a2d6b7e0a8 | ||
|
|
95d65481e3 | ||
|
|
71522243b5 | ||
|
|
953533d9a3 | ||
|
|
31509392c9 | ||
|
|
dc30469b2c | ||
|
|
fa2903ebb9 | ||
|
|
c43387bf6a | ||
|
|
47f5cbeac9 | ||
|
|
2aea68fac4 | ||
|
|
9f7e6559d3 | ||
|
|
e352e3125c | ||
|
|
6b5e93f5ad | ||
|
|
cea5ae9c6c | ||
|
|
80b2bbf1eb | ||
|
|
c87f25d7bf | ||
|
|
82aff804a6 | ||
|
|
973e67acdb | ||
|
|
93bd9daf76 | ||
|
|
d8c121662c | ||
|
|
5f33249c5d | ||
|
|
1beafc9976 | ||
|
|
b3857adc0f | ||
|
|
e75010692c | ||
|
|
f2df4a1560 | ||
|
|
7a4b68de97 | ||
|
|
6fce430469 | ||
|
|
8856094dd1 | ||
|
|
20bee16d28 | ||
|
|
b1cd971a21 | ||
|
|
d976a39711 | ||
|
|
accd0236ea | ||
|
|
fc401a1426 | ||
|
|
7e8c09a684 | ||
|
|
37ddff86f1 | ||
|
|
4dc25fb71e | ||
|
|
42883fbe22 | ||
|
|
a88d9a07be | ||
|
|
544f1e482a | ||
|
|
f53d4c8951 | ||
|
|
70d8aa5b33 | ||
|
|
1aa4be9000 | ||
|
|
a7c7c73f70 | ||
|
|
0b441021d8 | ||
|
|
7db0c8cf62 | ||
|
|
6da9cb6dcf | ||
|
|
0af580eb26 | ||
|
|
52085bdc1c | ||
|
|
72f1eea721 | ||
|
|
6d52b022b2 | ||
|
|
ea41c9b0b3 | ||
|
|
ed6127c2bb | ||
|
|
b6d95a84fc | ||
|
|
c71e68bdea | ||
|
|
c44583f249 | ||
|
|
c53773c2db | ||
|
|
793994a3fe | ||
|
|
4c3deb1156 | ||
|
|
24ca5a5ace | ||
|
|
8bbbf57827 | ||
|
|
888df02d0c | ||
|
|
8988765cef | ||
|
|
f675b88761 | ||
|
|
9bd4975478 | ||
|
|
95abb7bfae | ||
|
|
d9fa3dcc3b | ||
|
|
964524816f | ||
|
|
d124c9be18 | ||
|
|
1a05402076 | ||
|
|
c4b1790102 | ||
|
|
613acbff95 | ||
|
|
e6ab98ad11 | ||
|
|
1121869f14 | ||
|
|
91efe57e1b | ||
|
|
95f2573263 | ||
|
|
09aa85f51c | ||
|
|
c6279a1076 | ||
|
|
47ccb64cfb | ||
|
|
5c218567e1 | ||
|
|
c161d84fdf | ||
|
|
495b553285 | ||
|
|
21b770ba8b | ||
|
|
e9f94e0767 | ||
|
|
644ada1da9 | ||
|
|
0c40250849 | ||
|
|
1d1134a86d | ||
|
|
28e7664eb7 | ||
|
|
50f3ad641c | ||
|
|
cc7cf5f8c5 | ||
|
|
339f0f6e94 | ||
|
|
f558e43342 | ||
|
|
e374e5c90c | ||
|
|
563b775e49 | ||
|
|
de9e1a4515 | ||
|
|
f64b36f17a | ||
|
|
f0e8c82d31 | ||
|
|
5770d43230 | ||
|
|
d4944c236f | ||
|
|
33c761a187 | ||
|
|
d7e6da8d2c | ||
|
|
44d1a2415c | ||
|
|
c98ff50f06 | ||
|
|
8835fcb09e | ||
|
|
77c56e58c0 | ||
|
|
72c65ca4ee | ||
|
|
ab019b0bdc | ||
|
|
9709e45ad2 | ||
|
|
be1f80003c | ||
|
|
252fcca383 | ||
|
|
04ae8fa4a0 | ||
|
|
c95bd7776a | ||
|
|
8219167d05 | ||
|
|
e0a6881343 | ||
|
|
6e985d7f06 | ||
|
|
66719b05dd | ||
|
|
7197583fea | ||
|
|
ce29024eef | ||
|
|
e1ac67f7fa | ||
|
|
01812144dd | ||
|
|
1c34e49629 | ||
|
|
f233fbfb25 | ||
|
|
5387115e4a | ||
|
|
d82c03db23 | ||
|
|
230c5c3766 | ||
|
|
927425149e | ||
|
|
5ce1aab92c | ||
|
|
195742bb26 | ||
|
|
006cc2912d | ||
|
|
2d4ba90c3b | ||
|
|
a2e6aaaa18 | ||
|
|
8e68da7725 | ||
|
|
7abb84c880 | ||
|
|
a17878f5b2 | ||
|
|
8a8881ac47 | ||
|
|
c567404b7a | ||
|
|
b220b0f48e | ||
|
|
9609c90d75 | ||
|
|
2c3c32af5b | ||
|
|
b4a4b2e9b1 | ||
|
|
c42ff1e1e9 | ||
|
|
9fed1141c2 | ||
|
|
e87f031293 | ||
|
|
c4bac7f43c | ||
|
|
47818f972e | ||
|
|
218a0300c5 | ||
|
|
63f6c4177f | ||
|
|
1830c22a31 | ||
|
|
18611e8a7c | ||
|
|
c45f7adf04 | ||
|
|
1a200918a8 | ||
|
|
b942bb776e | ||
|
|
5cf84efccd | ||
|
|
ebb6ebd10c | ||
|
|
42d0d63cf4 | ||
|
|
96f8f7e925 | ||
|
|
e7e7214d58 | ||
|
|
ade979a725 | ||
|
|
60a8de13e7 | ||
|
|
9fa24bed0a | ||
|
|
87bc1a7e03 | ||
|
|
1a05f56149 | ||
|
|
f88db576e1 | ||
|
|
dc3f26ea1a | ||
|
|
6fc30144f7 | ||
|
|
25b0b98bd4 | ||
|
|
27b5817d5e | ||
|
|
dcb61dfd33 | ||
|
|
bbcfdbbf5e | ||
|
|
b2a1bef08f | ||
|
|
2b18b5c2ca | ||
|
|
6ff030dbd8 | ||
|
|
0ddeef6986 | ||
|
|
976bd3600b | ||
|
|
a64047a934 | ||
|
|
e82f207935 | ||
|
|
61b5316a1f | ||
|
|
82329aa8b0 | ||
|
|
7dabd9c19c | ||
|
|
9437acd18c | ||
|
|
9da7a34edf | ||
|
|
b6a5491dcc | ||
|
|
bcee658567 | ||
|
|
afc8f7b703 | ||
|
|
7a4b89d2fb | ||
|
|
c6299a2fb0 | ||
|
|
8b5d74af9b | ||
|
|
a194360a56 | ||
|
|
b12f7f69ba | ||
|
|
06ec4d3fba | ||
|
|
c209ab912f | ||
|
|
32720d772d | ||
|
|
a89c02fd10 | ||
|
|
37ef86b92f | ||
|
|
4c19c37f49 | ||
|
|
1bb818b5b0 | ||
|
|
825e46458f | ||
|
|
a42737bd28 | ||
|
|
5f76be2cfd | ||
|
|
dbddf8a91a | ||
|
|
6c457f41f6 | ||
|
|
e4b2a650f0 | ||
|
|
913ba95801 | ||
|
|
a9f8e39703 | ||
|
|
534f013f59 | ||
|
|
258380f75c | ||
|
|
8c0e51ec46 | ||
|
|
4c37c7ab84 | ||
|
|
f005da1d5f | ||
|
|
e99acc4694 | ||
|
|
408357dfcf | ||
|
|
0109a27c06 | ||
|
|
e6e2dccc42 | ||
|
|
09dcf0d712 | ||
|
|
60aebd9306 | ||
|
|
04191d04d3 | ||
|
|
b80a5c525f | ||
|
|
265c1e5312 | ||
|
|
2723f705b6 | ||
|
|
b4cddd6341 | ||
|
|
5636a81d48 | ||
|
|
d8059960de | ||
|
|
17af4064af | ||
|
|
15f37d2c93 | ||
|
|
6dc3aa8cb7 | ||
|
|
900cccf2f1 | ||
|
|
1fec88dfc6 | ||
|
|
7da9363336 | ||
|
|
d82e633bba | ||
|
|
b363bbaafd | ||
|
|
92a20e3c9a | ||
|
|
5742dfb263 | ||
|
|
0ae63511d5 | ||
|
|
aa60092c20 | ||
|
|
54fc265d24 | ||
|
|
a5ac900784 | ||
|
|
4053f1da32 | ||
|
|
0374ccd8a8 | ||
|
|
1d46c446cf | ||
|
|
54b66805f9 | ||
|
|
f7afcbde92 | ||
|
|
8bec1cf68e | ||
|
|
2cd1bb7f95 | ||
|
|
19e6329a2b | ||
|
|
fce2879567 | ||
|
|
0973765919 | ||
|
|
827679721e | ||
|
|
735279bc7a | ||
|
|
3eb2ed9897 | ||
|
|
3a913d98c7 | ||
|
|
9bfcd79e36 | ||
|
|
a81d610302 | ||
|
|
64b1753c4d | ||
|
|
afcb5c2957 | ||
|
|
7d0b9208a3 | ||
|
|
c0f0ec43bb | ||
|
|
30bd66958c | ||
|
|
fafac1a038 | ||
|
|
f979f9503e | ||
|
|
b233c3cc7a | ||
|
|
597ac936f7 | ||
|
|
a590254eb3 | ||
|
|
0498bcf30c | ||
|
|
59f9b5c724 | ||
|
|
80729935b6 | ||
|
|
4ca57fb99c | ||
|
|
9b35902ad4 | ||
|
|
3b8bd09190 | ||
|
|
71a5bc0652 | ||
|
|
ac6a8c4e85 | ||
|
|
f58a808c3a | ||
|
|
51037be772 | ||
|
|
443ff9aff7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*_plus.go
|
*_plus.go
|
||||||
|
*_plus_test.go
|
||||||
*-plus.sh
|
*-plus.sh
|
||||||
@@ -50,11 +50,12 @@ function build() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
||||||
|
cp "$ROOT"/configs/cluster.template.yaml "$DIST"/configs
|
||||||
cp -R "$ROOT"/www "$DIST"/
|
cp -R "$ROOT"/www "$DIST"/
|
||||||
cp -R "$ROOT"/pages "$DIST"/
|
cp -R "$ROOT"/pages "$DIST"/
|
||||||
|
|
||||||
# we support TOA on linux/amd64 only
|
# we support TOA on linux/amd64 only
|
||||||
if [ "$OS" == "linux" -a "$ARCH" == "amd64" ]
|
if [ "$OS" == "linux" ] && [ "$ARCH" == "amd64" ]
|
||||||
then
|
then
|
||||||
cp -R "$ROOT"/edge-toa "$DIST"
|
cp -R "$ROOT"/edge-toa "$DIST"
|
||||||
fi
|
fi
|
||||||
@@ -113,7 +114,10 @@ function build() {
|
|||||||
if [ ! -z $CC_PATH ]; then
|
if [ ! -z $CC_PATH ]; then
|
||||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||||
else
|
else
|
||||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||||
|
BUILD_TAG="plus,script"
|
||||||
|
fi
|
||||||
|
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# delete hidden files
|
# delete hidden files
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
* `global.yaml` - 全局配置
|
* `api.template.yaml` - API相关配置模板
|
||||||
|
* `cluster.template.yaml` - 通过集群自动接入节点模板
|
||||||
9
build/test.sh
Executable file
9
build/test.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
TAG=${1}
|
||||||
|
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
TAG="community"
|
||||||
|
fi
|
||||||
|
|
||||||
|
go test -v ../... -tags=${TAG}
|
||||||
@@ -25,7 +25,7 @@ func main() {
|
|||||||
Product(teaconst.ProductName).
|
Product(teaconst.ProductName).
|
||||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
|
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
|
||||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
||||||
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove] IP")
|
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
|
||||||
|
|
||||||
app.On("test", func() {
|
app.On("test", func() {
|
||||||
err := nodes.NewNode().Test()
|
err := nodes.NewNode().Test()
|
||||||
@@ -241,6 +241,38 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
app.On("ip.close", func() {
|
||||||
|
var args = os.Args[2:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Println("Usage: edge-node ip.close IP")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ip = args[0]
|
||||||
|
if len(net.ParseIP(ip)) == 0 {
|
||||||
|
fmt.Println("IP '" + ip + "' is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("close ip '" + ip)
|
||||||
|
|
||||||
|
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||||
|
reply, err := sock.Send(&gosock.Command{
|
||||||
|
Code: "closeIP",
|
||||||
|
Params: map[string]any{
|
||||||
|
"ip": ip,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[ERROR]" + err.Error())
|
||||||
|
} else {
|
||||||
|
var errString = maps.NewMap(reply.Params).GetString("error")
|
||||||
|
if len(errString) > 0 {
|
||||||
|
fmt.Println("[ERROR]" + errString)
|
||||||
|
} else {
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
app.On("ip.remove", func() {
|
app.On("ip.remove", func() {
|
||||||
var args = os.Args[2:]
|
var args = os.Args[2:]
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
|||||||
76
go.mod
76
go.mod
@@ -5,60 +5,78 @@ go 1.18
|
|||||||
replace (
|
replace (
|
||||||
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||||
github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4
|
github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4
|
||||||
|
github.com/google/nftables => github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||||
github.com/andybalholm/brotli v1.0.4
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
|
||||||
|
github.com/andybalholm/brotli v1.0.5
|
||||||
|
github.com/aws/aws-sdk-go v1.44.279
|
||||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||||
github.com/cespare/xxhash v1.1.0
|
github.com/cespare/xxhash v1.1.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/fsnotify/fsnotify v1.5.1
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.3
|
||||||
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6
|
github.com/google/nftables v0.1.0
|
||||||
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55
|
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
|
||||||
|
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
|
||||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5
|
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508
|
||||||
github.com/klauspost/compress v1.15.8
|
github.com/klauspost/compress v1.16.5
|
||||||
github.com/mattn/go-sqlite3 v1.14.9
|
github.com/mattn/go-sqlite3 v1.14.9
|
||||||
github.com/mdlayher/netlink v1.4.2
|
github.com/mdlayher/netlink v1.7.1
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/mssola/user_agent v0.5.3
|
github.com/mssola/useragent v1.0.0
|
||||||
github.com/pires/go-proxyproto v0.6.1
|
github.com/pires/go-proxyproto v0.6.1
|
||||||
|
github.com/qiniu/go-sdk/v7 v7.16.0
|
||||||
|
github.com/quic-go/quic-go v0.36.1
|
||||||
github.com/shirou/gopsutil/v3 v3.22.2
|
github.com/shirou/gopsutil/v3 v3.22.2
|
||||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
golang.org/x/image v0.7.0
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
golang.org/x/net v0.12.0
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/sys v0.10.0
|
||||||
google.golang.org/grpc v1.45.0
|
google.golang.org/grpc v1.55.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
rogchap.com/v8go v0.7.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
|
||||||
github.com/chai2010/webp v1.1.1 // indirect
|
github.com/chai2010/webp v1.1.1 // indirect
|
||||||
|
github.com/clbanning/mxj v1.8.4 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/google/go-cmp v0.5.7 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/google/go-querystring v1.0.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
|
github.com/mdlayher/socket v0.4.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||||
|
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
|
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
|
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||||
|
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/mod v0.5.1 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/tools v0.1.8 // indirect
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
golang.org/x/text v0.11.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
honnef.co/go/tools v0.2.2 // indirect
|
golang.org/x/tools v0.11.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
383
go.sum
383
go.sum
@@ -1,32 +1,23 @@
|
|||||||
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=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
|
||||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
|
||||||
|
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||||
github.com/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=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||||
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-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
|
||||||
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=
|
||||||
@@ -35,296 +26,240 @@ github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ
|
|||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
||||||
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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
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.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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/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/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6 h1:btadZscaRmsi/+fOhkyUguRpSnrf6dykNEWxDeUCj9I=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6/go.mod h1:0F8on3JWMkm+xahTHItkiu/E1SPqMd0TOxNweQv8ptE=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55 h1:shQNx0flJFBwKsGE7Hs3bI2bDz+YF0zl/4qE8B2KRiY=
|
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
|
||||||
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
|
||||||
|
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
|
||||||
|
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g=
|
||||||
|
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
|
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
|
||||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
|
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
|
||||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
||||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
||||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5 h1:SgKhrqRhWVpu0ZKxQM8MGjdhy7hlH3Xax8snwsZWSic=
|
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508 h1:fjKiHAyPQmdwuw1DQ2BI1JTbhUWAtI3Kr9wIZQBdRgQ=
|
||||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
|
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA=
|
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||||
github.com/klauspost/compress v1.15.8/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
|
||||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
|
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
|
||||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
|
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
|
||||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
|
||||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
|
||||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
|
||||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
|
||||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
|
||||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
|
||||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
|
||||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
|
||||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
|
||||||
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
|
|
||||||
github.com/mdlayher/netlink v1.4.2 h1:3sbnJWe/LETovA7yRZIX3f9McVOWV3OySH6iIBxiFfI=
|
|
||||||
github.com/mdlayher/netlink v1.4.2/go.mod h1:13VaingaArGUTUxFLf/iEovKxXji32JAtF858jZYEug=
|
|
||||||
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
|
|
||||||
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
|
||||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY=
|
|
||||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
|
||||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||||
github.com/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578=
|
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||||
github.com/mssola/user_agent v0.5.3/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
|
||||||
|
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
|
||||||
|
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
|
||||||
|
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||||
|
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/qiniu/go-sdk/v7 v7.16.0 h1:Jt4YOMLuaDfgb/KdVg0O1fYLpv5MDkYe/zV+Ri7gWRs=
|
||||||
|
github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
|
||||||
|
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||||
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
|
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
|
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
|
||||||
|
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
|
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
|
||||||
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
||||||
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/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||||
|
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||||
|
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||||
|
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||||
|
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||||
|
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4=
|
||||||
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc=
|
||||||
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.41/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw=
|
||||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
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/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||||
|
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
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-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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
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-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-20201020160332-67f06af15bc9/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-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-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
||||||
|
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
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-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
|
|
||||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
|
||||||
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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
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.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
|
||||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
|
||||||
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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.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/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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
|
||||||
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
|
|
||||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
|
||||||
rogchap.com/v8go v0.7.0 h1:kgjbiO4zE5itA962ze6Hqmbs4HgZbGzmueCXsZtremg=
|
|
||||||
rogchap.com/v8go v0.7.0/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package apps
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"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/TeaGo/types"
|
||||||
@@ -215,7 +216,7 @@ func (this *AppCmd) runStop() {
|
|||||||
|
|
||||||
// 从systemd中停止
|
// 从systemd中停止
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
systemctl, _ := exec.LookPath("systemctl")
|
systemctl, _ := executils.LookPath("systemctl")
|
||||||
if len(systemctl) > 0 {
|
if len(systemctl) > 0 {
|
||||||
go func() {
|
go func() {
|
||||||
// 有可能会长时间执行,这里不阻塞进程
|
// 有可能会长时间执行,这里不阻塞进程
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (this *LogWriter) Init() {
|
|||||||
this.c = make(chan string, 1024)
|
this.c = make(chan string, 1024)
|
||||||
|
|
||||||
// 异步写入文件
|
// 异步写入文件
|
||||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
var maxFileSize = 128 * sizes.M // 文件最大尺寸,超出此尺寸则清空
|
||||||
if fp != nil {
|
if fp != nil {
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
var totalSize int64 = 0
|
var totalSize int64 = 0
|
||||||
|
|||||||
11
internal/apps/main.go
Normal file
11
internal/apps/main.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package apps
|
||||||
|
|
||||||
|
import teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
|
|
||||||
|
func RunMain(f func()) {
|
||||||
|
if teaconst.IsMain {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
SuffixAll = "@GOEDGE_" // 通用后缀
|
||||||
SuffixWebP = "@GOEDGE_WEBP" // WebP后缀
|
SuffixWebP = "@GOEDGE_WEBP" // WebP后缀
|
||||||
SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding
|
SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding
|
||||||
SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod
|
SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod
|
||||||
|
|||||||
11
internal/caches/file_dir.go
Normal file
11
internal/caches/file_dir.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
|
|
||||||
|
type FileDir struct {
|
||||||
|
Path string
|
||||||
|
Capacity *shared.SizeCapacity
|
||||||
|
IsFull bool
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ type Item struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *Item) IsExpired() bool {
|
func (this *Item) IsExpired() bool {
|
||||||
return this.ExpiredAt < utils.UnixTime()
|
return this.ExpiredAt < fasttime.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Item) TotalSize() int64 {
|
func (this *Item) TotalSize() int64 {
|
||||||
@@ -59,3 +60,17 @@ func (this *Item) IncreaseHit(week int32) {
|
|||||||
this.Week = week
|
this.Week = week
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Item) RequestURI() string {
|
||||||
|
var schemeIndex = strings.Index(this.Key, "://")
|
||||||
|
if schemeIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstSlashIndex = strings.Index(this.Key[schemeIndex+3:], "/")
|
||||||
|
if firstSlashIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Key[schemeIndex+3+firstSlashIndex:]
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,3 +81,14 @@ func TestItems_Memory2(t *testing.T) {
|
|||||||
t.Log(w, len(i))
|
t.Log(w, len(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestItem_RequestURI(t *testing.T) {
|
||||||
|
for _, u := range []string{
|
||||||
|
"https://goedge.cn/hello/world",
|
||||||
|
"https://goedge.cn:8080/hello/world",
|
||||||
|
"https://goedge.cn/hello/world?v=1&t=123",
|
||||||
|
} {
|
||||||
|
var item = &Item{Key: u}
|
||||||
|
t.Log(u, "=>", item.RequestURI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"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/dbs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,7 +20,6 @@ const CountFileDB = 20
|
|||||||
type FileList struct {
|
type FileList struct {
|
||||||
dir string
|
dir string
|
||||||
dbList [CountFileDB]*FileListDB
|
dbList [CountFileDB]*FileListDB
|
||||||
total int64
|
|
||||||
|
|
||||||
onAdd func(item *Item)
|
onAdd func(item *Item)
|
||||||
onRemove func(item *Item)
|
onRemove func(item *Item)
|
||||||
@@ -76,12 +74,6 @@ func (this *FileList) Init() error {
|
|||||||
this.dbList[i] = db
|
this.dbList[i] = db
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取总数量
|
|
||||||
this.total = 0
|
|
||||||
for _, db := range this.dbList {
|
|
||||||
this.total += db.total
|
|
||||||
}
|
|
||||||
|
|
||||||
// 升级老版本数据库
|
// 升级老版本数据库
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
this.upgradeOldDB()
|
this.upgradeOldDB()
|
||||||
@@ -102,13 +94,11 @@ func (this *FileList) Add(hash string, item *Item) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.AddAsync(hash, item)
|
err := db.AddSync(hash, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddInt64(&this.total, 1)
|
|
||||||
|
|
||||||
// 这里不增加点击量,以减少对数据库的操作次数
|
// 这里不增加点击量,以减少对数据库的操作次数
|
||||||
|
|
||||||
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
||||||
@@ -160,6 +150,7 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// TODO 需要优化
|
||||||
this.memoryCache.Clean()
|
this.memoryCache.Clean()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -172,6 +163,46 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
|
||||||
|
func (this *FileList) CleanMatchKey(key string) error {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// TODO 需要优化
|
||||||
|
this.memoryCache.Clean()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
err := db.CleanMatchKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
|
||||||
|
func (this *FileList) CleanMatchPrefix(prefix string) error {
|
||||||
|
if len(prefix) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// TODO 需要优化
|
||||||
|
this.memoryCache.Clean()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
err := db.CleanMatchPrefix(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileList) Remove(hash string) error {
|
func (this *FileList) Remove(hash string) error {
|
||||||
_, err := this.remove(hash)
|
_, err := this.remove(hash)
|
||||||
return err
|
return err
|
||||||
@@ -255,8 +286,6 @@ func (this *FileList) CleanAll() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.StoreInt64(&this.total, 0)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +320,15 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
|||||||
// Count 总数量
|
// Count 总数量
|
||||||
// 常用的方法,所以避免直接查询数据库
|
// 常用的方法,所以避免直接查询数据库
|
||||||
func (this *FileList) Count() (int64, error) {
|
func (this *FileList) Count() (int64, error) {
|
||||||
return atomic.LoadInt64(&this.total), nil
|
var total int64
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
count, err := db.Total()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
total += count
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncreaseHit 增加点击量
|
// IncreaseHit 增加点击量
|
||||||
@@ -351,37 +388,19 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
|
|||||||
// 从缓存中删除
|
// 从缓存中删除
|
||||||
this.memoryCache.Delete(hash)
|
this.memoryCache.Delete(hash)
|
||||||
|
|
||||||
var row = db.selectByHashStmt.QueryRow(hash)
|
err = db.DeleteSync(hash)
|
||||||
if row.Err() != nil {
|
|
||||||
if row.Err() == sql.ErrNoRows {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, row.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = &Item{Type: ItemTypeFile}
|
|
||||||
err = row.Scan(&item.Key, &item.HeaderSize, &item.BodySize, &item.MetaSize, &item.ExpiredAt)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.DeleteAsync(hash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, db.WrapError(err)
|
return false, db.WrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddInt64(&this.total, -1)
|
|
||||||
|
|
||||||
err = db.DeleteHitAsync(hash)
|
err = db.DeleteHitAsync(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, db.WrapError(err)
|
return false, db.WrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.onRemove != nil {
|
if this.onRemove != nil {
|
||||||
this.onRemove(item)
|
// when remove file item, no any extra information needed
|
||||||
|
this.onRemove(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
@@ -409,7 +428,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
|||||||
remotelogs.Println("CACHE", "upgrading local database finished")
|
remotelogs.Println("CACHE", "upgrading local database finished")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+indexDBPath+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,20 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"errors"
|
"errors"
|
||||||
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/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/TeaOSLab/EdgeNode/internal/utils/dbs"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -30,8 +35,6 @@ type FileListDB struct {
|
|||||||
itemsTableName string
|
itemsTableName string
|
||||||
hitsTableName string
|
hitsTableName string
|
||||||
|
|
||||||
total int64
|
|
||||||
|
|
||||||
isClosed bool
|
isClosed bool
|
||||||
isReady bool
|
isReady bool
|
||||||
|
|
||||||
@@ -48,16 +51,16 @@ type FileListDB struct {
|
|||||||
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
||||||
deleteByHashSQL string
|
deleteByHashSQL string
|
||||||
|
|
||||||
statStmt *dbs.Stmt // 统计
|
statStmt *dbs.Stmt // 统计
|
||||||
purgeStmt *dbs.Stmt // 清理
|
purgeStmt *dbs.Stmt // 清理
|
||||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||||
|
updateAccessWeekSQL string // 修改访问日期
|
||||||
|
|
||||||
// hits
|
// hits
|
||||||
insertHitSQL string // 写入数据
|
insertHitSQL string // 写入数据
|
||||||
increaseHitSQL string // 增加点击量
|
increaseHitSQL string // 增加点击量
|
||||||
deleteHitByHashSQL string // 根据hash删除数据
|
deleteHitByHashSQL string // 根据hash删除数据
|
||||||
lfuHitsStmt *dbs.Stmt // 读取老的数据
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileListDB() *FileListDB {
|
func NewFileListDB() *FileListDB {
|
||||||
@@ -77,24 +80,37 @@ func (this *FileListDB) Open(dbPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write db
|
// write db
|
||||||
writeDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size="+types.String(cacheSize)+"&_secure_delete=FAST")
|
// 这里不能加 EXCLUSIVE 锁,不然异步事务可能会失败
|
||||||
|
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("open write database failed: " + err.Error())
|
return errors.New("open write database failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDB.SetMaxOpenConns(1)
|
writeDB.SetMaxOpenConns(1)
|
||||||
|
|
||||||
|
this.writeDB = writeDB
|
||||||
|
|
||||||
// TODO 耗时过长,暂时不整理数据库
|
// TODO 耗时过长,暂时不整理数据库
|
||||||
// TODO 需要根据行数来判断是否VACUUM
|
// TODO 需要根据行数来判断是否VACUUM
|
||||||
|
// TODO 注意VACUUM反而可能让数据库文件变大
|
||||||
/**_, err = db.Exec("VACUUM")
|
/**_, err = db.Exec("VACUUM")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}**/
|
}**/
|
||||||
|
|
||||||
this.writeDB = dbs.NewDB(writeDB)
|
// 检查是否损坏
|
||||||
|
// TODO 暂时屏蔽,因为用时过长
|
||||||
|
|
||||||
|
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||||
|
if len(recoverEnv) > 0 && this.shouldRecover() {
|
||||||
|
for _, indexName := range []string{"staleAt", "hash"} {
|
||||||
|
_, _ = this.writeDB.Exec(`REINDEX "` + indexName + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.writeBatch = dbs.NewBatch(writeDB, 4)
|
this.writeBatch = dbs.NewBatch(writeDB, 4)
|
||||||
this.writeBatch.OnFail(func(err error) {
|
this.writeBatch.OnFail(func(err error) {
|
||||||
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error())
|
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
|
||||||
})
|
})
|
||||||
|
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
@@ -107,14 +123,14 @@ func (this *FileListDB) Open(dbPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// read db
|
// read db
|
||||||
readDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size="+types.String(cacheSize))
|
readDB, err := dbs.OpenReader("file:" + dbPath + "?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("open read database failed: " + err.Error())
|
return errors.New("open read database failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
readDB.SetMaxOpenConns(runtime.NumCPU())
|
readDB.SetMaxOpenConns(runtime.NumCPU())
|
||||||
|
|
||||||
this.readDB = dbs.NewDB(readDB)
|
this.readDB = readDB
|
||||||
|
|
||||||
if teaconst.EnableDBStat {
|
if teaconst.EnableDBStat {
|
||||||
this.readDB.EnableStat(true)
|
this.readDB.EnableStat(true)
|
||||||
@@ -133,25 +149,13 @@ func (this *FileListDB) Init() error {
|
|||||||
return errors.New("init tables failed: " + err.Error())
|
return errors.New("init tables failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取总数量
|
|
||||||
row := this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
|
||||||
if row.Err() != nil {
|
|
||||||
return row.Err()
|
|
||||||
}
|
|
||||||
var total int64
|
|
||||||
err = row.Scan(&total)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
this.total = total
|
|
||||||
|
|
||||||
// 常用语句
|
// 常用语句
|
||||||
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt", "accessWeek") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
|
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -163,6 +167,9 @@ func (this *FileListDB) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
|
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
|
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
|
||||||
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)
|
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)
|
||||||
@@ -185,7 +192,12 @@ func (this *FileListDB) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
|
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "accessWeek" ASC, "id" ASC LIMIT ?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
|
||||||
|
|
||||||
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
||||||
|
|
||||||
@@ -193,11 +205,6 @@ func (this *FileListDB) Init() error {
|
|||||||
|
|
||||||
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
||||||
|
|
||||||
this.lfuHitsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.hitsTableName + `" ORDER BY "week" ASC, "week1Hits"+"week2Hits" ASC LIMIT ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isReady = true
|
this.isReady = true
|
||||||
|
|
||||||
// 加载HashMap
|
// 加载HashMap
|
||||||
@@ -205,6 +212,20 @@ func (this *FileListDB) Init() error {
|
|||||||
err := this.hashMap.Load(this)
|
err := this.hashMap.Load(this)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||||
|
|
||||||
|
// 自动修复错误
|
||||||
|
// TODO 将来希望能尽可能恢复以往数据库中的内容
|
||||||
|
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
|
||||||
|
_ = this.Close()
|
||||||
|
this.deleteDB()
|
||||||
|
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
|
||||||
|
err = this.Open(this.dbPath)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
|
||||||
|
} else {
|
||||||
|
_ = this.Init()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -215,20 +236,15 @@ func (this *FileListDB) IsReady() bool {
|
|||||||
return this.isReady
|
return this.isReady
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) Total() int64 {
|
func (this *FileListDB) Total() (int64, error) {
|
||||||
return this.total
|
// 读取总数量
|
||||||
}
|
var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||||
|
if row.Err() != nil {
|
||||||
func (this *FileListDB) AddAsync(hash string, item *Item) error {
|
return 0, row.Err()
|
||||||
this.hashMap.Add(hash)
|
|
||||||
|
|
||||||
if item.StaleAt == 0 {
|
|
||||||
item.StaleAt = item.ExpiredAt
|
|
||||||
}
|
}
|
||||||
|
var total int64
|
||||||
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
|
err := row.Scan(&total)
|
||||||
return nil
|
return total, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) AddSync(hash string, item *Item) error {
|
func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||||
@@ -238,7 +254,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
|||||||
item.StaleAt = item.ExpiredAt
|
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())
|
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return this.WrapError(err)
|
return this.WrapError(err)
|
||||||
}
|
}
|
||||||
@@ -246,13 +262,6 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) DeleteAsync(hash string) error {
|
|
||||||
this.hashMap.Delete(hash)
|
|
||||||
|
|
||||||
this.writeBatch.Add(this.deleteByHashSQL, hash)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *FileListDB) DeleteSync(hash string) error {
|
func (this *FileListDB) DeleteSync(hash string) error {
|
||||||
this.hashMap.Delete(hash)
|
this.hashMap.Delete(hash)
|
||||||
|
|
||||||
@@ -300,22 +309,23 @@ func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
|
|||||||
count = 100
|
count = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
hashList, err = this.listLFUItems(count)
|
// 先找过期的
|
||||||
|
hashList, err = this.ListExpiredItems(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var l = len(hashList)
|
||||||
|
|
||||||
if len(hashList) > count/2 {
|
// 从旧缓存中补充
|
||||||
return
|
if l < count {
|
||||||
|
oldHashList, err := this.listOlderItems(count - l)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashList = append(hashList, oldHashList...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不足补齐
|
return hashList, nil
|
||||||
olderHashList, err := this.listOlderItems(count - len(hashList))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hashList = append(hashList, olderHashList...)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
|
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
|
||||||
@@ -342,6 +352,7 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
|
|||||||
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
||||||
var week = timeutil.Format("YW")
|
var week = timeutil.Format("YW")
|
||||||
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
||||||
|
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,8 +366,8 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var count = int64(10000)
|
var count = int64(10000)
|
||||||
var staleLife = 600 // TODO 需要可以设置
|
var staleLife = 600 // TODO 需要可以设置
|
||||||
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||||
for {
|
for {
|
||||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -372,6 +383,85 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) CleanMatchKey(key string) error {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略 @GOEDGE_
|
||||||
|
if strings.Contains(key, SuffixAll) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转义
|
||||||
|
var queryKey = strings.ReplaceAll(key, "%", "\\%")
|
||||||
|
queryKey = strings.ReplaceAll(queryKey, "_", "\\_")
|
||||||
|
queryKey = strings.Replace(queryKey, "*", "%", 1)
|
||||||
|
|
||||||
|
// TODO 检查大批量数据下的操作性能
|
||||||
|
var staleLife = 600 // TODO 需要可以设置
|
||||||
|
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||||
|
|
||||||
|
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转义
|
||||||
|
var queryPrefix = strings.ReplaceAll(prefix, "%", "\\%")
|
||||||
|
queryPrefix = strings.ReplaceAll(queryPrefix, "_", "\\_")
|
||||||
|
queryPrefix = strings.Replace(queryPrefix, "*", "%", 1)
|
||||||
|
queryPrefix += "%"
|
||||||
|
|
||||||
|
// TODO 检查大批量数据下的操作性能
|
||||||
|
var staleLife = 600 // TODO 需要可以设置
|
||||||
|
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||||
|
|
||||||
|
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileListDB) CleanAll() error {
|
func (this *FileListDB) CleanAll() error {
|
||||||
if !this.isReady {
|
if !this.isReady {
|
||||||
return nil
|
return nil
|
||||||
@@ -388,6 +478,10 @@ func (this *FileListDB) CleanAll() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) Close() error {
|
func (this *FileListDB) Close() error {
|
||||||
|
if this.isClosed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
this.isClosed = true
|
this.isClosed = true
|
||||||
this.isReady = false
|
this.isReady = false
|
||||||
|
|
||||||
@@ -418,9 +512,6 @@ func (this *FileListDB) Close() error {
|
|||||||
if this.listOlderItemsStmt != nil {
|
if this.listOlderItemsStmt != nil {
|
||||||
_ = this.listOlderItemsStmt.Close()
|
_ = this.listOlderItemsStmt.Close()
|
||||||
}
|
}
|
||||||
if this.lfuHitsStmt != nil {
|
|
||||||
_ = this.lfuHitsStmt.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var errStrings []string
|
var errStrings []string
|
||||||
|
|
||||||
@@ -438,10 +529,6 @@ func (this *FileListDB) Close() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.writeBatch != nil {
|
|
||||||
this.writeBatch.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errStrings) == 0 {
|
if len(errStrings) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -460,6 +547,7 @@ func (this *FileListDB) initTables(times int) error {
|
|||||||
{
|
{
|
||||||
// expiredAt - 过期时间,用来判断有无过期
|
// expiredAt - 过期时间,用来判断有无过期
|
||||||
// staleAt - 过时缓存最大时间,用来清理缓存
|
// staleAt - 过时缓存最大时间,用来清理缓存
|
||||||
|
// 不对 hash 增加 unique 参数,是尽可能避免产生 malformed 错误
|
||||||
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
_, err := this.writeDB.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),
|
||||||
@@ -472,7 +560,8 @@ func (this *FileListDB) initTables(times int) error {
|
|||||||
"staleAt" 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,
|
||||||
|
"accessWeek" varchar(6)
|
||||||
);
|
);
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "createdAt";
|
DROP INDEX IF EXISTS "createdAt";
|
||||||
@@ -484,23 +573,32 @@ ON "` + this.itemsTableName + `" (
|
|||||||
"staleAt" ASC
|
"staleAt" ASC
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
CREATE INDEX IF NOT EXISTS "hash"
|
||||||
ON "` + this.itemsTableName + `" (
|
ON "` + this.itemsTableName + `" (
|
||||||
"hash" ASC
|
"hash" ASC
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 尝试删除重建
|
// 忽略可以预期的错误
|
||||||
if times < 3 {
|
if strings.Contains(err.Error(), "duplicate column name") {
|
||||||
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
err = nil
|
||||||
if dropErr == nil {
|
|
||||||
return this.initTables(times + 1)
|
|
||||||
}
|
|
||||||
return this.WrapError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.WrapError(err)
|
// 尝试删除重建
|
||||||
|
if err != nil {
|
||||||
|
if times < 3 {
|
||||||
|
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
||||||
|
if dropErr == nil {
|
||||||
|
return this.initTables(times + 1)
|
||||||
|
}
|
||||||
|
return this.WrapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.WrapError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,27 +633,6 @@ ON "` + this.hitsTableName + `" (
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) listLFUItems(count int) (hashList []string, err error) {
|
|
||||||
rows, err := this.lfuHitsStmt.Query(count)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = rows.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var hash string
|
|
||||||
err = rows.Scan(&hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hashList = append(hashList, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
|
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
|
||||||
rows, err := this.listOlderItemsStmt.Query(count)
|
rows, err := this.listOlderItemsStmt.Query(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -576,3 +653,28 @@ func (this *FileListDB) listOlderItems(count int) (hashList []string, err error)
|
|||||||
|
|
||||||
return hashList, nil
|
return hashList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) shouldRecover() bool {
|
||||||
|
result, err := this.writeDB.Query("pragma integrity_check;")
|
||||||
|
if err != nil {
|
||||||
|
logs.Println(result)
|
||||||
|
}
|
||||||
|
var errString = ""
|
||||||
|
var shouldRecover = false
|
||||||
|
for result.Next() {
|
||||||
|
err = result.Scan(&errString)
|
||||||
|
if strings.TrimSpace(errString) != "ok" {
|
||||||
|
shouldRecover = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = result.Close()
|
||||||
|
return shouldRecover
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除数据库文件
|
||||||
|
func (this *FileListDB) deleteDB() {
|
||||||
|
_ = os.Remove(this.dbPath)
|
||||||
|
_ = os.Remove(this.dbPath + "-shm")
|
||||||
|
_ = os.Remove(this.dbPath + "-wal")
|
||||||
|
}
|
||||||
|
|||||||
107
internal/caches/list_file_db_test.go
Normal file
107
internal/caches/list_file_db_test.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
_ "github.com/iwind/TeaGo/bootstrap"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
//err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = db.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashList, err := db.ListLFUItems(100)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("[", len(hashList), "]", hashList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Init()
|
||||||
|
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait transaction
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Init()
|
||||||
|
|
||||||
|
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CleanMatchKey("https://*.goedge.cn:1234/large-text?%2B____")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Init()
|
||||||
|
|
||||||
|
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CleanMatchPrefix("https://*.goedge.cn:1234/large-text?%2B____")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,6 +107,12 @@ func (this *FileListHashMap) IsReady() bool {
|
|||||||
return this.isReady
|
return this.isReady
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileListHashMap) Len() int {
|
||||||
|
this.locker.Lock()
|
||||||
|
defer this.locker.Unlock()
|
||||||
|
return len(this.m)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileListHashMap) bigInt(hash string) uint64 {
|
func (this *FileListHashMap) bigInt(hash string) uint64 {
|
||||||
var bigInt = big.NewInt(0)
|
var bigInt = big.NewInt(0)
|
||||||
bigInt.SetString(hash, 16)
|
bigInt.SetString(hash, 16)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFileListHashMap_Memory(t *testing.T) {
|
func TestFileListHashMap_Memory(t *testing.T) {
|
||||||
@@ -58,20 +59,25 @@ func TestFileListHashMap_BigInt(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileListHashMap_Load(t *testing.T) {
|
func TestFileListHashMap_Load(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
err := list.Init()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = list.Close()
|
_ = list.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var m = caches.NewFileListHashMap()
|
err := list.Init()
|
||||||
err = m.Load(list.GetDB("abc"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var m = caches.NewFileListHashMap()
|
||||||
|
var before = time.Now()
|
||||||
|
var db = list.GetDB("abc")
|
||||||
|
err = m.Load(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
t.Log("count:", m.Len())
|
||||||
m.Add("abc")
|
m.Add("abc")
|
||||||
|
|
||||||
for _, hash := range []string{"33347bb4441265405347816cad36a0f8", "a", "abc", "123"} {
|
for _, hash := range []string{"33347bb4441265405347816cad36a0f8", "a", "abc", "123"} {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package caches_test
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||||
"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"
|
||||||
@@ -17,6 +18,11 @@ import (
|
|||||||
|
|
||||||
func TestFileList_Init(t *testing.T) {
|
func TestFileList_Init(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -29,6 +35,11 @@ func TestFileList_Init(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_Add(t *testing.T) {
|
func TestFileList_Add(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -59,16 +70,21 @@ func TestFileList_Add(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Add_Many(t *testing.T) {
|
func TestFileList_Add_Many(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
if !testutils.IsSingleTesting() {
|
||||||
err := list.Init()
|
return
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = list.Close()
|
_ = list.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
err := list.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
var before = time.Now()
|
var before = time.Now()
|
||||||
for i := 0; i < 10_000_000; i++ {
|
for i := 0; i < 10_000_000; i++ {
|
||||||
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||||
@@ -92,15 +108,15 @@ func TestFileList_Add_Many(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_Exist(t *testing.T) {
|
func TestFileList_Exist(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = list.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
total, _ := list.Count()
|
total, _ := list.Count()
|
||||||
t.Log("total:", total)
|
t.Log("total:", total)
|
||||||
|
|
||||||
@@ -130,7 +146,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
|||||||
// 测试在多个数据库下的性能
|
// 测试在多个数据库下的性能
|
||||||
var listSlice = []caches.ListInterface{}
|
var listSlice = []caches.ListInterface{}
|
||||||
for i := 1; i <= 10; i++ {
|
for i := 1; i <= 10; i++ {
|
||||||
list := caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
var list = caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -138,6 +154,12 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
|||||||
listSlice = append(listSlice, list)
|
listSlice = append(listSlice, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, list := range listSlice {
|
||||||
|
_ = list.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var wg = sync.WaitGroup{}
|
var wg = sync.WaitGroup{}
|
||||||
var threads = 8
|
var threads = 8
|
||||||
wg.Add(threads)
|
wg.Add(threads)
|
||||||
@@ -181,15 +203,16 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_CleanPrefix(t *testing.T) {
|
func TestFileList_CleanPrefix(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = list.Close()
|
_ = list.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
err := list.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
err = list.CleanPrefix("123")
|
err = list.CleanPrefix("123")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -200,15 +223,15 @@ func TestFileList_CleanPrefix(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_Remove(t *testing.T) {
|
func TestFileList_Remove(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = list.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
list.OnRemove(func(item *caches.Item) {
|
list.OnRemove(func(item *caches.Item) {
|
||||||
t.Logf("remove %#v", item)
|
t.Logf("remove %#v", item)
|
||||||
})
|
})
|
||||||
@@ -224,13 +247,15 @@ func TestFileList_Remove(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_Purge(t *testing.T) {
|
func TestFileList_Purge(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
_ = list.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
_, err = list.Purge(caches.CountFileDB*2, func(hash string) error {
|
_, err = list.Purge(caches.CountFileDB*2, func(hash string) error {
|
||||||
@@ -246,13 +271,15 @@ func TestFileList_Purge(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_PurgeLFU(t *testing.T) {
|
func TestFileList_PurgeLFU(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
_ = list.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = list.IncreaseHit(stringutil.Md5("123456"))
|
err = list.IncreaseHit(stringutil.Md5("123456"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -273,15 +300,16 @@ func TestFileList_PurgeLFU(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_Stat(t *testing.T) {
|
func TestFileList_Stat(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = list.Close()
|
_ = list.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
err := list.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
stat, err := list.Stat(nil)
|
stat, err := list.Stat(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -291,6 +319,11 @@ func TestFileList_Stat(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_Count(t *testing.T) {
|
func TestFileList_Count(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -305,7 +338,12 @@ func TestFileList_Count(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_CleanAll(t *testing.T) {
|
func TestFileList_CleanAll(t *testing.T) {
|
||||||
list := caches.NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -320,6 +358,11 @@ func TestFileList_CleanAll(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_IncreaseHit(t *testing.T) {
|
func TestFileList_IncreaseHit(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -333,7 +376,13 @@ func TestFileList_IncreaseHit(t *testing.T) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
}()
|
}()
|
||||||
for i := 0; i < 1000_000; i++ {
|
|
||||||
|
var count = 1_000_000
|
||||||
|
|
||||||
|
if !testutils.IsSingleTesting() {
|
||||||
|
count = 10
|
||||||
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -344,6 +393,11 @@ func TestFileList_IncreaseHit(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileList_UpgradeV3(t *testing.T) {
|
func TestFileList_UpgradeV3(t *testing.T) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -363,6 +417,11 @@ func TestFileList_UpgradeV3(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkFileList_Exist(b *testing.B) {
|
func BenchmarkFileList_Exist(b *testing.B) {
|
||||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ type ListInterface interface {
|
|||||||
// CleanPrefix 清除某个前缀的缓存
|
// CleanPrefix 清除某个前缀的缓存
|
||||||
CleanPrefix(prefix string) error
|
CleanPrefix(prefix string) error
|
||||||
|
|
||||||
|
// CleanMatchKey 清除通配符匹配的Key
|
||||||
|
CleanMatchKey(key string) error
|
||||||
|
|
||||||
|
// CleanMatchPrefix 清除通配符匹配的前缀
|
||||||
|
CleanMatchPrefix(prefix string) error
|
||||||
|
|
||||||
// Remove 删除内容
|
// Remove 删除内容
|
||||||
Remove(hash string) error
|
Remove(hash string) error
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -146,6 +149,82 @@ func (this *MemoryList) CleanPrefix(prefix string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
|
||||||
|
func (this *MemoryList) CleanMatchKey(key string) error {
|
||||||
|
if strings.Contains(key, SuffixAll) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var requestURI = u.RequestURI()
|
||||||
|
|
||||||
|
this.locker.RLock()
|
||||||
|
defer this.locker.RUnlock()
|
||||||
|
|
||||||
|
// TODO 需要优化性能,支持千万级数据低于1s的处理速度
|
||||||
|
for _, itemMap := range this.itemMaps {
|
||||||
|
for _, item := range itemMap {
|
||||||
|
if configutils.MatchDomain(host, item.Host) {
|
||||||
|
var itemRequestURI = item.RequestURI()
|
||||||
|
if itemRequestURI == requestURI || strings.HasPrefix(itemRequestURI, requestURI+SuffixAll) {
|
||||||
|
item.ExpiredAt = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
|
||||||
|
func (this *MemoryList) CleanMatchPrefix(prefix string) error {
|
||||||
|
u, err := url.Parse(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var requestURI = u.RequestURI()
|
||||||
|
var isRootPath = requestURI == "/"
|
||||||
|
|
||||||
|
this.locker.RLock()
|
||||||
|
defer this.locker.RUnlock()
|
||||||
|
|
||||||
|
// TODO 需要优化性能,支持千万级数据低于1s的处理速度
|
||||||
|
for _, itemMap := range this.itemMaps {
|
||||||
|
for _, item := range itemMap {
|
||||||
|
if configutils.MatchDomain(host, item.Host) {
|
||||||
|
var itemRequestURI = item.RequestURI()
|
||||||
|
if isRootPath || strings.HasPrefix(itemRequestURI, requestURI) {
|
||||||
|
item.ExpiredAt = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (this *MemoryList) Remove(hash string) error {
|
func (this *MemoryList) Remove(hash string) error {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
@@ -107,7 +108,9 @@ func TestMemoryList_Purge_Large_List(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(1 * time.Hour)
|
if testutils.IsSingleTesting() {
|
||||||
|
time.Sleep(1 * time.Hour)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryList_Stat(t *testing.T) {
|
func TestMemoryList_Stat(t *testing.T) {
|
||||||
@@ -255,9 +258,11 @@ func TestMemoryList_GC(t *testing.T) {
|
|||||||
//runtime.GC()
|
//runtime.GC()
|
||||||
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
|
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
|
||||||
|
|
||||||
timeout := time.NewTimer(2 * time.Minute)
|
if testutils.IsSingleTesting() {
|
||||||
<-timeout.C
|
timeout := time.NewTimer(2 * time.Minute)
|
||||||
t.Log("2 minutes passed")
|
<-timeout.C
|
||||||
|
t.Log("2 minutes passed")
|
||||||
|
|
||||||
time.Sleep(30 * time.Minute)
|
time.Sleep(30 * time.Minute)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"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"
|
||||||
@@ -14,7 +15,11 @@ import (
|
|||||||
var SharedManager = NewManager()
|
var SharedManager = NewManager()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
events.On(events.EventQuit, func() {
|
if !teaconst.IsMain {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
events.OnClose(func() {
|
||||||
remotelogs.Println("CACHE", "quiting cache manager")
|
remotelogs.Println("CACHE", "quiting cache manager")
|
||||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
||||||
})
|
})
|
||||||
@@ -24,7 +29,8 @@ func init() {
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
// 全局配置
|
// 全局配置
|
||||||
MaxDiskCapacity *shared.SizeCapacity
|
MaxDiskCapacity *shared.SizeCapacity
|
||||||
DiskDir string
|
MainDiskDir string
|
||||||
|
SubDiskDirs []*serverconfigs.CacheDir
|
||||||
MaxMemoryCapacity *shared.SizeCapacity
|
MaxMemoryCapacity *shared.SizeCapacity
|
||||||
|
|
||||||
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
||||||
@@ -47,12 +53,10 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
|||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
newPolicyIds := []int64{}
|
var newPolicyIds = []int64{}
|
||||||
for _, policy := range newPolicies {
|
for _, policy := range newPolicies {
|
||||||
// 使用节点单独的缓存目录
|
// 使用节点单独的缓存目录
|
||||||
if len(this.DiskDir) > 0 {
|
policy.UpdateDiskDir(this.MainDiskDir, this.SubDiskDirs)
|
||||||
policy.UpdateDiskDir(this.DiskDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
newPolicyIds = append(newPolicyIds, policy.Id)
|
newPolicyIds = append(newPolicyIds, policy.Id)
|
||||||
}
|
}
|
||||||
@@ -173,10 +177,15 @@ func (this *Manager) TotalDiskSize() int64 {
|
|||||||
this.locker.RLock()
|
this.locker.RLock()
|
||||||
defer this.locker.RUnlock()
|
defer this.locker.RUnlock()
|
||||||
|
|
||||||
total := int64(0)
|
var total = int64(0)
|
||||||
for _, storage := range this.storageMap {
|
for _, storage := range this.storageMap {
|
||||||
total += storage.TotalDiskSize()
|
total += storage.TotalDiskSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if total < 0 {
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,8 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
MaxKeys: 1,
|
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
@@ -95,9 +94,9 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
|
|||||||
func TestManager_ChangePolicy_File(t *testing.T) {
|
func TestManager_ChangePolicy_File(t *testing.T) {
|
||||||
var policies = []*serverconfigs.HTTPCachePolicy{
|
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||||
{
|
{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/data/cache-index/p1",
|
"dir": Tea.Root + "/data/cache-index/p1",
|
||||||
},
|
},
|
||||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||||
@@ -106,9 +105,9 @@ func TestManager_ChangePolicy_File(t *testing.T) {
|
|||||||
SharedManager.UpdatePolicies(policies)
|
SharedManager.UpdatePolicies(policies)
|
||||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||||
{
|
{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/data/cache-index/p1",
|
"dir": Tea.Root + "/data/cache-index/p1",
|
||||||
},
|
},
|
||||||
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type OpenFileCache struct {
|
|||||||
poolList *linkedlist.List
|
poolList *linkedlist.List
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
|
|
||||||
locker sync.Mutex
|
locker sync.RWMutex
|
||||||
|
|
||||||
maxSize int
|
maxSize int
|
||||||
count int
|
count int
|
||||||
@@ -54,13 +54,18 @@ func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *OpenFileCache) Get(filename string) *OpenFile {
|
func (this *OpenFileCache) Get(filename string) *OpenFile {
|
||||||
this.locker.Lock()
|
this.locker.RLock()
|
||||||
defer this.locker.Unlock()
|
|
||||||
pool, ok := this.poolMap[filename]
|
pool, ok := this.poolMap[filename]
|
||||||
|
this.locker.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
file, consumed := pool.Get()
|
file, consumed := pool.Get()
|
||||||
if consumed {
|
if consumed {
|
||||||
|
this.locker.Lock()
|
||||||
this.count--
|
this.count--
|
||||||
|
|
||||||
|
// pool如果为空,也不需要从列表中删除,避免put时需要重新创建
|
||||||
|
|
||||||
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
@@ -124,6 +129,9 @@ func (this *OpenFileCache) Close(filename string) {
|
|||||||
|
|
||||||
pool, ok := this.poolMap[filename]
|
pool, ok := this.poolMap[filename]
|
||||||
if ok {
|
if ok {
|
||||||
|
// 设置关闭状态
|
||||||
|
pool.SetClosing()
|
||||||
|
|
||||||
delete(this.poolMap, filename)
|
delete(this.poolMap, filename)
|
||||||
this.poolList.Remove(pool.linkItem)
|
this.poolList.Remove(pool.linkItem)
|
||||||
_ = this.watcher.Remove(filename)
|
_ = this.watcher.Remove(filename)
|
||||||
@@ -146,6 +154,7 @@ func (this *OpenFileCache) CloseAll() {
|
|||||||
this.poolMap = map[string]*OpenFilePool{}
|
this.poolMap = map[string]*OpenFilePool{}
|
||||||
this.poolList.Reset()
|
this.poolList.Reset()
|
||||||
_ = this.watcher.Close()
|
_ = this.watcher.Close()
|
||||||
|
this.count = 0
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
internal/caches/open_file_cache_test.go
Normal file
46
internal/caches/open_file_cache_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewOpenFileCache_Close(t *testing.T) {
|
||||||
|
cache, err := caches.NewOpenFileCache(1024)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cache.Debug()
|
||||||
|
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Get("b.txt")
|
||||||
|
cache.Get("d.txt")
|
||||||
|
cache.Close("a.txt")
|
||||||
|
|
||||||
|
if testutils.IsSingleTesting() {
|
||||||
|
time.Sleep(100 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewOpenFileCache_CloseAll(t *testing.T) {
|
||||||
|
cache, err := caches.NewOpenFileCache(1024)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cache.Debug()
|
||||||
|
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Get("b.txt")
|
||||||
|
cache.Get("d.txt")
|
||||||
|
cache.CloseAll()
|
||||||
|
|
||||||
|
time.Sleep(6 * time.Second)
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,13 +12,14 @@ type OpenFilePool struct {
|
|||||||
linkItem *linkedlist.Item
|
linkItem *linkedlist.Item
|
||||||
filename string
|
filename string
|
||||||
version int64
|
version int64
|
||||||
|
isClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOpenFilePool(filename string) *OpenFilePool {
|
func NewOpenFilePool(filename string) *OpenFilePool {
|
||||||
var pool = &OpenFilePool{
|
var pool = &OpenFilePool{
|
||||||
filename: filename,
|
filename: filename,
|
||||||
c: make(chan *OpenFile, 1024),
|
c: make(chan *OpenFile, 1024),
|
||||||
version: utils.UnixTimeMilli(),
|
version: fasttime.Now().UnixMilli(),
|
||||||
}
|
}
|
||||||
pool.linkItem = linkedlist.NewItem(pool)
|
pool.linkItem = linkedlist.NewItem(pool)
|
||||||
return pool
|
return pool
|
||||||
@@ -29,26 +30,43 @@ func (this *OpenFilePool) Filename() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *OpenFilePool) Get() (*OpenFile, bool) {
|
func (this *OpenFilePool) Get() (*OpenFile, bool) {
|
||||||
|
// 如果已经关闭,直接返回
|
||||||
|
if this.isClosed {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case file := <-this.c:
|
case file := <-this.c:
|
||||||
err := file.SeekStart()
|
if file != nil {
|
||||||
if err != nil {
|
err := file.SeekStart()
|
||||||
_ = file.Close()
|
if err != nil {
|
||||||
return nil, true
|
_ = file.Close()
|
||||||
}
|
return nil, true
|
||||||
file.version = this.version
|
}
|
||||||
|
file.version = this.version
|
||||||
|
|
||||||
return file, true
|
return file, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
default:
|
default:
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *OpenFilePool) Put(file *OpenFile) bool {
|
func (this *OpenFilePool) Put(file *OpenFile) bool {
|
||||||
|
// 如果已关闭,则不接受新的文件
|
||||||
|
if this.isClosed {
|
||||||
|
_ = file.Close()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件版本号
|
||||||
if this.version > 0 && file.version > 0 && file.version != this.version {
|
if this.version > 0 && file.version > 0 && file.version != this.version {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加入Pool
|
||||||
select {
|
select {
|
||||||
case this.c <- file:
|
case this.c <- file:
|
||||||
return true
|
return true
|
||||||
@@ -63,14 +81,18 @@ func (this *OpenFilePool) Len() int {
|
|||||||
return len(this.c)
|
return len(this.c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *OpenFilePool) SetClosing() {
|
||||||
|
this.isClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
func (this *OpenFilePool) Close() {
|
func (this *OpenFilePool) Close() {
|
||||||
Loop:
|
this.isClosed = true
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case file := <-this.c:
|
case file := <-this.c:
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
default:
|
default:
|
||||||
break Loop
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package caches_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,3 +17,30 @@ func TestOpenFilePool_Get(t *testing.T) {
|
|||||||
t.Log(pool.Get())
|
t.Log(pool.Get())
|
||||||
t.Log(pool.Get())
|
t.Log(pool.Get())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenFilePool_Close(t *testing.T) {
|
||||||
|
var pool = caches.NewOpenFilePool("a")
|
||||||
|
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
pool.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenFilePool_Concurrent(t *testing.T) {
|
||||||
|
var pool = caches.NewOpenFilePool("a")
|
||||||
|
var concurrent = 1000
|
||||||
|
var wg = &sync.WaitGroup{}
|
||||||
|
wg.Add(concurrent)
|
||||||
|
for i := 0; i < concurrent; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if rands.Int(0, 1) == 1 {
|
||||||
|
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
}
|
||||||
|
if rands.Int(0, 1) == 0 {
|
||||||
|
pool.Get()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,38 +3,88 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"github.com/iwind/TeaGo/types"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PartialRanges 内容分区范围定义
|
// PartialRanges 内容分区范围定义
|
||||||
type PartialRanges struct {
|
type PartialRanges struct {
|
||||||
Ranges [][2]int64 `json:"ranges"`
|
Version int `json:"version"` // 版本号
|
||||||
|
Ranges [][2]int64 `json:"ranges"` // 范围
|
||||||
|
BodySize int64 `json:"bodySize"` // 总长度
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialRanges 获取新对象
|
// NewPartialRanges 获取新对象
|
||||||
func NewPartialRanges() *PartialRanges {
|
func NewPartialRanges(expiresAt int64) *PartialRanges {
|
||||||
return &PartialRanges{Ranges: [][2]int64{}}
|
return &PartialRanges{
|
||||||
|
Ranges: [][2]int64{},
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPartialRangesFromData 从数据中解析范围
|
||||||
|
func NewPartialRangesFromData(data []byte) (*PartialRanges, error) {
|
||||||
|
var rs = NewPartialRanges(0)
|
||||||
|
for {
|
||||||
|
var index = bytes.IndexRune(data, '\n')
|
||||||
|
if index < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var line = data[:index]
|
||||||
|
var colonIndex = bytes.IndexRune(line, ':')
|
||||||
|
if colonIndex > 0 {
|
||||||
|
switch string(line[:colonIndex]) {
|
||||||
|
case "v": // 版本号
|
||||||
|
rs.Version = types.Int(line[colonIndex+1:])
|
||||||
|
case "b": // 总长度
|
||||||
|
rs.BodySize = types.Int64(line[colonIndex+1:])
|
||||||
|
case "r": // 范围信息
|
||||||
|
var commaIndex = bytes.IndexRune(line, ',')
|
||||||
|
if commaIndex > 0 {
|
||||||
|
rs.Ranges = append(rs.Ranges, [2]int64{types.Int64(line[colonIndex+1 : commaIndex]), types.Int64(line[commaIndex+1:])})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = data[index+1:]
|
||||||
|
if len(data) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialRangesFromJSON 从JSON中解析范围
|
// NewPartialRangesFromJSON 从JSON中解析范围
|
||||||
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
||||||
var rs = NewPartialRanges()
|
var rs = NewPartialRanges(0)
|
||||||
err := json.Unmarshal(data, &rs)
|
err := json.Unmarshal(data, &rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
rs.Version = 0
|
||||||
|
|
||||||
return rs, nil
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPartialRangesFromFile 从文件中加载范围信息
|
||||||
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewPartialRangesFromJSON(data)
|
if len(data) == 0 {
|
||||||
|
return NewPartialRanges(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容老的JSON格式
|
||||||
|
if data[0] == '{' {
|
||||||
|
return NewPartialRangesFromJSON(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新的格式
|
||||||
|
return NewPartialRangesFromData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 添加新范围
|
// Add 添加新范围
|
||||||
@@ -105,29 +155,27 @@ func (this *PartialRanges) Nearest(begin int64, end int64) (r [2]int64, ok bool)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsJSON 转换为JSON
|
// 转换为字符串
|
||||||
func (this *PartialRanges) AsJSON() ([]byte, error) {
|
func (this *PartialRanges) String() string {
|
||||||
return json.Marshal(this)
|
var s = "v:" + strconv.Itoa(this.Version) + "\n" + // version
|
||||||
|
"b:" + this.formatInt64(this.BodySize) + "\n" // bodySize
|
||||||
|
for _, r := range this.Ranges {
|
||||||
|
s += "r:" + this.formatInt64(r[0]) + "," + this.formatInt64(r[1]) + "\n" // range
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes 将内容转换为字节
|
||||||
|
func (this *PartialRanges) Bytes() []byte {
|
||||||
|
return []byte(this.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToFile 写入到文件中
|
// WriteToFile 写入到文件中
|
||||||
func (this *PartialRanges) WriteToFile(path string) error {
|
func (this *PartialRanges) WriteToFile(path string) error {
|
||||||
data, err := this.AsJSON()
|
return os.WriteFile(path, this.Bytes(), 0666)
|
||||||
if err != nil {
|
|
||||||
return errors.New("convert to json failed: " + err.Error())
|
|
||||||
}
|
|
||||||
return os.WriteFile(path, data, 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFromFile 从文件中读取
|
|
||||||
func (this *PartialRanges) ReadFromFile(path string) (*PartialRanges, error) {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewPartialRangesFromJSON(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Max 获取最大位置
|
||||||
func (this *PartialRanges) Max() int64 {
|
func (this *PartialRanges) Max() int64 {
|
||||||
if len(this.Ranges) > 0 {
|
if len(this.Ranges) > 0 {
|
||||||
return this.Ranges[len(this.Ranges)-1][1]
|
return this.Ranges[len(this.Ranges)-1][1]
|
||||||
@@ -135,6 +183,11 @@ func (this *PartialRanges) Max() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset 重置范围信息
|
||||||
|
func (this *PartialRanges) Reset() {
|
||||||
|
this.Ranges = [][2]int64{}
|
||||||
|
}
|
||||||
|
|
||||||
func (this *PartialRanges) merge(index int) {
|
func (this *PartialRanges) merge(index int) {
|
||||||
// forward
|
// forward
|
||||||
var lastIndex = index
|
var lastIndex = index
|
||||||
@@ -187,3 +240,7 @@ func (this *PartialRanges) max(n1 int64, n2 int64) int64 {
|
|||||||
}
|
}
|
||||||
return n2
|
return n2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PartialRanges) formatInt64(i int64) string {
|
||||||
|
return strconv.FormatInt(i, 10)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
package caches_test
|
package caches_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
"github.com/iwind/TeaGo/assert"
|
"github.com/iwind/TeaGo/assert"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewPartialRanges(t *testing.T) {
|
func TestNewPartialRanges(t *testing.T) {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, 100)
|
r.Add(1, 100)
|
||||||
r.Add(50, 300)
|
r.Add(50, 300)
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ func TestNewPartialRanges(t *testing.T) {
|
|||||||
func TestNewPartialRanges1(t *testing.T) {
|
func TestNewPartialRanges1(t *testing.T) {
|
||||||
var a = assert.NewAssertion(t)
|
var a = assert.NewAssertion(t)
|
||||||
|
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, 100)
|
r.Add(1, 100)
|
||||||
r.Add(1, 101)
|
r.Add(1, 101)
|
||||||
r.Add(1, 102)
|
r.Add(1, 102)
|
||||||
@@ -47,7 +49,7 @@ func TestNewPartialRanges1(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewPartialRanges2(t *testing.T) {
|
func TestNewPartialRanges2(t *testing.T) {
|
||||||
// low -> high
|
// low -> high
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, 100)
|
r.Add(1, 100)
|
||||||
r.Add(1, 101)
|
r.Add(1, 101)
|
||||||
r.Add(1, 102)
|
r.Add(1, 102)
|
||||||
@@ -63,7 +65,7 @@ func TestNewPartialRanges2(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewPartialRanges3(t *testing.T) {
|
func TestNewPartialRanges3(t *testing.T) {
|
||||||
// high -> low
|
// high -> low
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 302)
|
r.Add(301, 302)
|
||||||
r.Add(303, 304)
|
r.Add(303, 304)
|
||||||
r.Add(200, 300)
|
r.Add(200, 300)
|
||||||
@@ -75,7 +77,7 @@ func TestNewPartialRanges3(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewPartialRanges4(t *testing.T) {
|
func TestNewPartialRanges4(t *testing.T) {
|
||||||
// nearby
|
// nearby
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 302)
|
r.Add(301, 302)
|
||||||
r.Add(303, 304)
|
r.Add(303, 304)
|
||||||
r.Add(305, 306)
|
r.Add(305, 306)
|
||||||
@@ -90,7 +92,7 @@ func TestNewPartialRanges4(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPartialRanges5(t *testing.T) {
|
func TestNewPartialRanges5(t *testing.T) {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
for j := 0; j < 1000; j++ {
|
for j := 0; j < 1000; j++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Add(int64(j), int64(j+100))
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ func TestNewPartialRanges5(t *testing.T) {
|
|||||||
func TestNewPartialRanges_Nearest(t *testing.T) {
|
func TestNewPartialRanges_Nearest(t *testing.T) {
|
||||||
{
|
{
|
||||||
// nearby
|
// nearby
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 400)
|
r.Add(301, 400)
|
||||||
r.Add(401, 500)
|
r.Add(401, 500)
|
||||||
r.Add(501, 600)
|
r.Add(501, 600)
|
||||||
@@ -112,7 +114,7 @@ func TestNewPartialRanges_Nearest(t *testing.T) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// nearby
|
// nearby
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 400)
|
r.Add(301, 400)
|
||||||
r.Add(450, 500)
|
r.Add(450, 500)
|
||||||
r.Add(550, 600)
|
r.Add(550, 600)
|
||||||
@@ -131,45 +133,100 @@ func TestNewPartialRanges_Large_Range(t *testing.T) {
|
|||||||
var largeSize int64 = 10000000000000
|
var largeSize int64 = 10000000000000
|
||||||
t.Log(largeSize/1024/1024/1024, "G")
|
t.Log(largeSize/1024/1024/1024, "G")
|
||||||
|
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, largeSize)
|
r.Add(1, largeSize)
|
||||||
jsonData, err := r.AsJSON()
|
var s = r.String()
|
||||||
if err != nil {
|
t.Log(s)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(string(jsonData))
|
|
||||||
|
|
||||||
r2, err := caches.NewPartialRangesFromJSON(jsonData)
|
r2, err := caches.NewPartialRangesFromData([]byte(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.IsTrue(largeSize == r2.Ranges[0][1])
|
a.IsTrue(largeSize == r2.Ranges[0][1])
|
||||||
|
logs.PrintAsJSON(r, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPartialRanges_AsJSON(t *testing.T) {
|
func TestPartialRanges_Encode_JSON(t *testing.T) {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
for j := 0; j < 1000; j++ {
|
for i := 0; i < 10; i++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
|
||||||
}
|
}
|
||||||
data, err := r.AsJSON()
|
var before = time.Now()
|
||||||
|
data, err := json.Marshal(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log(string(data))
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
t.Log(len(data))
|
||||||
|
}
|
||||||
|
|
||||||
r2, err := caches.NewPartialRangesFromJSON(data)
|
func TestPartialRanges_Encode_String(t *testing.T) {
|
||||||
|
var r = caches.NewPartialRanges(0)
|
||||||
|
r.BodySize = 1024
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
|
||||||
|
}
|
||||||
|
var before = time.Now()
|
||||||
|
var data = r.String()
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
t.Log(len(data))
|
||||||
|
|
||||||
|
r2, err := caches.NewPartialRangesFromData([]byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log(r2.Ranges)
|
logs.PrintAsJSON(r2, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialRanges_Version(t *testing.T) {
|
||||||
|
{
|
||||||
|
ranges, err := caches.NewPartialRangesFromData([]byte(`e:1668928495
|
||||||
|
r:0,1048576
|
||||||
|
r:1140260864,1140295164`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("version:", ranges.Version)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ranges, err := caches.NewPartialRangesFromData([]byte(`e:1668928495
|
||||||
|
r:0,1048576
|
||||||
|
r:1140260864,1140295164
|
||||||
|
v:0
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("version:", ranges.Version)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ranges, err := caches.NewPartialRangesFromJSON([]byte(`{}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("version:", ranges.Version)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkNewPartialRanges(b *testing.B) {
|
func BenchmarkNewPartialRanges(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
for j := 0; j < 1000; j++ {
|
for j := 0; j < 1000; j++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Add(int64(j), int64(j+100))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkPartialRanges_String(b *testing.B) {
|
||||||
|
var r = caches.NewPartialRanges(0)
|
||||||
|
r.BodySize = 1024
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = r.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
|
|||||||
this.header = this.openFile.header
|
this.header = this.openFile.header
|
||||||
}
|
}
|
||||||
|
|
||||||
isOk := false
|
var isOk = false
|
||||||
|
|
||||||
if autoDiscard {
|
if autoDiscard {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -67,17 +67,17 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
|
|||||||
|
|
||||||
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
|
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
|
||||||
|
|
||||||
status := types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus]))
|
var status = types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus]))
|
||||||
if status < 100 || status > 999 {
|
if status < 100 || status > 999 {
|
||||||
return errors.New("invalid status")
|
return errors.New("invalid status")
|
||||||
}
|
}
|
||||||
this.status = status
|
this.status = status
|
||||||
|
|
||||||
// URL
|
// URL
|
||||||
urlLength := binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength])
|
var urlLength = binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength])
|
||||||
|
|
||||||
// header
|
// header
|
||||||
headerSize := int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength]))
|
var headerSize = int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength]))
|
||||||
if headerSize == 0 {
|
if headerSize == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
|
|||||||
|
|
||||||
// body
|
// body
|
||||||
this.bodyOffset = this.headerOffset + int64(headerSize)
|
this.bodyOffset = this.headerOffset + int64(headerSize)
|
||||||
bodySize := int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength]))
|
var bodySize = int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength]))
|
||||||
if bodySize == 0 {
|
if bodySize == 0 {
|
||||||
isOk = true
|
isOk = true
|
||||||
return nil
|
return nil
|
||||||
@@ -158,7 +158,7 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isOk := false
|
var isOk = false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -171,7 +171,7 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
headerSize := this.headerSize
|
var headerSize = this.headerSize
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err := this.fp.Read(buf)
|
n, err := this.fp.Read(buf)
|
||||||
@@ -215,7 +215,11 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
||||||
isOk := false
|
if this.bodySize == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOk = false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -257,15 +261,22 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) Read(buf []byte) (n int, err error) {
|
func (this *FileReader) Read(buf []byte) (n int, err error) {
|
||||||
|
if this.bodySize == 0 {
|
||||||
|
n = 0
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, err = this.fp.Read(buf)
|
n, err = this.fp.Read(buf)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
_ = this.discard()
|
_ = this.discard()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
||||||
isOk := false
|
var isOk = false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -273,7 +284,7 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
offset := start
|
var offset = start
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
offset = this.bodyOffset + this.bodySize + end
|
offset = this.bodyOffset + this.bodySize + end
|
||||||
end = this.bodyOffset + this.bodySize - 1
|
end = this.bodyOffset + this.bodySize - 1
|
||||||
@@ -296,7 +307,7 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
|
|||||||
for {
|
for {
|
||||||
n, err := this.fp.Read(buf)
|
n, err := this.fp.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
n2 := int(end-offset) + 1
|
var n2 = int(end-offset) + 1
|
||||||
if n2 <= n {
|
if n2 <= n {
|
||||||
_, e := callback(n2)
|
_, e := callback(n2)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@@ -344,12 +355,12 @@ func (this *FileReader) FP() *os.File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) Close() error {
|
func (this *FileReader) Close() error {
|
||||||
if this.openFileCache != nil {
|
if this.isClosed {
|
||||||
if this.isClosed {
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
this.isClosed = true
|
||||||
this.isClosed = true
|
|
||||||
|
|
||||||
|
if this.openFileCache != nil {
|
||||||
if this.openFile != nil {
|
if this.openFile != nil {
|
||||||
this.openFileCache.Put(this.fp.Name(), this.openFile)
|
this.openFileCache.Put(this.fp.Name(), this.openFile)
|
||||||
} else {
|
} else {
|
||||||
@@ -359,6 +370,7 @@ func (this *FileReader) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fp.Close()
|
return this.fp.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFileReader(t *testing.T) {
|
func TestFileReader(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, path := storage.keyPath("my-key")
|
|
||||||
|
_, path, _ := storage.keyPath("my-key")
|
||||||
|
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Log("file '" + path + "' not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -58,6 +66,10 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
|||||||
var path = "/Users/WorkSpace/EdgeProject/EdgeCache/p43/12/6b/126bbed90fc80f2bdfb19558948b0d49.cache"
|
var path = "/Users/WorkSpace/EdgeProject/EdgeCache/p43/12/6b/126bbed90fc80f2bdfb19558948b0d49.cache"
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Log("'" + path + "' not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -66,6 +78,11 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
|||||||
var reader = NewFileReader(fp)
|
var reader = NewFileReader(fp)
|
||||||
err = reader.Init()
|
err = reader.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Log("file '" + path + "' not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var buf = make([]byte, 16*1024)
|
var buf = make([]byte, 16*1024)
|
||||||
@@ -79,13 +96,16 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileReader_Range(t *testing.T) {
|
func TestFileReader_Range(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -105,10 +125,14 @@ func TestFileReader_Range(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_ = writer.Close()**/
|
_ = writer.Close()**/
|
||||||
|
|
||||||
_, path := storage.keyPath("my-number")
|
_, path, _ := storage.keyPath("my-number")
|
||||||
|
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Log("'" + path + "' not exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@@ -117,13 +117,10 @@ func (this *PartialFileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.
|
|||||||
r2, ok = this.ranges.Nearest(r.Start(), r.End())
|
r2, ok = this.ranges.Nearest(r.Start(), r.End())
|
||||||
if ok && this.bodySize > 0 {
|
if ok && this.bodySize > 0 {
|
||||||
// 考虑可配置
|
// 考虑可配置
|
||||||
var span int64 = 512 * 1024
|
const minSpan = 128 << 10
|
||||||
if this.bodySize > 1<<30 {
|
|
||||||
span = 1 << 20
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多
|
// 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多
|
||||||
if r2.Length() < r.Length() && r2.Length() < span {
|
if r2.Length() < r.Length() && r2.Length() < minSpan {
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +135,10 @@ func (this *PartialFileReader) MaxLength() int64 {
|
|||||||
return this.ranges.Max() + 1
|
return this.ranges.Max() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileReader) Ranges() *PartialRanges {
|
||||||
|
return this.ranges
|
||||||
|
}
|
||||||
|
|
||||||
func (this *PartialFileReader) discard() error {
|
func (this *PartialFileReader) discard() error {
|
||||||
_ = os.Remove(this.rangePath)
|
_ = os.Remove(this.rangePath)
|
||||||
return this.FileReader.discard()
|
return this.FileReader.discard()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"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"
|
||||||
|
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
@@ -21,8 +22,6 @@ import (
|
|||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"golang.org/x/text/language"
|
|
||||||
"golang.org/x/text/message"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -31,7 +30,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -48,16 +46,16 @@ const (
|
|||||||
SizeBodyLength = 8
|
SizeBodyLength = 8
|
||||||
OffsetBodyLength = OffsetHeaderLength + SizeHeaderLength
|
OffsetBodyLength = OffsetHeaderLength + SizeHeaderLength
|
||||||
|
|
||||||
SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength
|
SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength
|
||||||
OffsetKey = SizeMeta
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||||
HotItemSize = 1024 // 热点数据数量
|
HotItemSize = 1024 // 热点数据数量
|
||||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||||
FileTmpSuffix = ".tmp"
|
FileTmpSuffix = ".tmp"
|
||||||
|
MinDiskSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||||
)
|
)
|
||||||
|
|
||||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||||
@@ -76,7 +74,6 @@ type FileStorage struct {
|
|||||||
policy *serverconfigs.HTTPCachePolicy
|
policy *serverconfigs.HTTPCachePolicy
|
||||||
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
||||||
memoryStorage *MemoryStorage // 一级缓存
|
memoryStorage *MemoryStorage // 一级缓存
|
||||||
totalSize int64
|
|
||||||
|
|
||||||
list ListInterface
|
list ListInterface
|
||||||
locker sync.RWMutex
|
locker sync.RWMutex
|
||||||
@@ -90,6 +87,10 @@ type FileStorage struct {
|
|||||||
ignoreKeys *setutils.FixedSet
|
ignoreKeys *setutils.FixedSet
|
||||||
|
|
||||||
openFileCache *OpenFileCache
|
openFileCache *OpenFileCache
|
||||||
|
|
||||||
|
mainDiskIsFull bool
|
||||||
|
|
||||||
|
subDirs []*FileDir
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
||||||
@@ -153,6 +154,16 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subDirs = []*FileDir{}
|
||||||
|
for _, subDir := range newOptions.SubDirs {
|
||||||
|
subDirs = append(subDirs, &FileDir{
|
||||||
|
Path: subDir.Path,
|
||||||
|
Capacity: subDir.Capacity,
|
||||||
|
IsFull: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.checkDiskSpace()
|
||||||
|
|
||||||
err = newOptions.Init()
|
err = newOptions.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error())
|
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error())
|
||||||
@@ -179,7 +190,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
|||||||
// open cache
|
// open cache
|
||||||
oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache)
|
oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache)
|
||||||
newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache)
|
newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache)
|
||||||
if bytes.Compare(oldOpenFileCacheJSON, newOpenFileCacheJSON) != 0 {
|
if !bytes.Equal(oldOpenFileCacheJSON, newOpenFileCacheJSON) {
|
||||||
this.initOpenFileCache()
|
this.initOpenFileCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +198,9 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
|||||||
if newPolicy.PersistenceAutoPurgeInterval != this.policy.PersistenceAutoPurgeInterval {
|
if newPolicy.PersistenceAutoPurgeInterval != this.policy.PersistenceAutoPurgeInterval {
|
||||||
this.initPurgeTicker()
|
this.initPurgeTicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset ignored keys
|
||||||
|
this.ignoreKeys.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init 初始化
|
// Init 初始化
|
||||||
@@ -215,6 +229,19 @@ func (this *FileStorage) Init() error {
|
|||||||
this.options.Dir = filepath.Clean(this.options.Dir)
|
this.options.Dir = filepath.Clean(this.options.Dir)
|
||||||
var dir = this.options.Dir
|
var dir = this.options.Dir
|
||||||
|
|
||||||
|
var subDirs = []*FileDir{}
|
||||||
|
for _, subDir := range this.options.SubDirs {
|
||||||
|
subDirs = append(subDirs, &FileDir{
|
||||||
|
Path: subDir.Path,
|
||||||
|
Capacity: subDir.Capacity,
|
||||||
|
IsFull: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.subDirs = subDirs
|
||||||
|
if len(subDirs) > 0 {
|
||||||
|
this.checkDiskSpace()
|
||||||
|
}
|
||||||
|
|
||||||
if len(dir) == 0 {
|
if len(dir) == 0 {
|
||||||
return errors.New("[CACHE]cache storage dir can not be empty")
|
return errors.New("[CACHE]cache storage dir can not be empty")
|
||||||
}
|
}
|
||||||
@@ -226,19 +253,6 @@ func (this *FileStorage) Init() error {
|
|||||||
}
|
}
|
||||||
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||||
this.list = list
|
this.list = list
|
||||||
stat, err := list.Stat(func(hash string) bool {
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
this.totalSize = stat.Size
|
|
||||||
this.list.OnAdd(func(item *Item) {
|
|
||||||
atomic.AddInt64(&this.totalSize, item.TotalSize())
|
|
||||||
})
|
|
||||||
this.list.OnRemove(func(item *Item) {
|
|
||||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查目录是否存在
|
// 检查目录是否存在
|
||||||
_, err = os.Stat(dir)
|
_, err = os.Stat(dir)
|
||||||
@@ -255,19 +269,17 @@ func (this *FileStorage) Init() error {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// 统计
|
// 统计
|
||||||
var count = stat.Count
|
var totalSize = this.TotalDiskSize()
|
||||||
var size = stat.Size
|
|
||||||
|
|
||||||
var cost = time.Since(before).Seconds() * 1000
|
var cost = time.Since(before).Seconds() * 1000
|
||||||
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
var sizeMB = types.String(totalSize) + " Bytes"
|
||||||
if size > 1*sizes.G {
|
if totalSize > 1*sizes.G {
|
||||||
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
|
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G))
|
||||||
} else if size > 1*sizes.M {
|
} else if totalSize > 1*sizes.M {
|
||||||
sizeMB = fmt.Sprintf("%.3f M", float64(size)/float64(sizes.M))
|
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M))
|
||||||
} else if size > 1*sizes.K {
|
} else if totalSize > 1*sizes.K {
|
||||||
sizeMB = fmt.Sprintf("%.3f K", float64(size)/float64(sizes.K))
|
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K))
|
||||||
}
|
}
|
||||||
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
|
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 初始化list
|
// 初始化list
|
||||||
@@ -287,6 +299,9 @@ func (this *FileStorage) Init() error {
|
|||||||
// open file cache
|
// open file cache
|
||||||
this.initOpenFileCache()
|
this.initOpenFileCache()
|
||||||
|
|
||||||
|
// 检查磁盘空间
|
||||||
|
this.checkDiskSpace()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +329,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, path := this.keyPath(key)
|
hash, path, _ := this.keyPath(key)
|
||||||
|
|
||||||
// 检查文件记录是否已过期
|
// 检查文件记录是否已过期
|
||||||
if !useStale {
|
if !useStale {
|
||||||
@@ -382,35 +397,35 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenWriter 打开缓存文件等待写入
|
// OpenWriter 打开缓存文件等待写入
|
||||||
func (this *FileStorage) OpenWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) {
|
func (this *FileStorage) OpenWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||||
return this.openWriter(key, expiresAt, status, size, maxSize, isPartial, false)
|
return this.openWriter(key, expiresAt, status, headerSize, bodySize, maxSize, isPartial, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
||||||
func (this *FileStorage) OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) {
|
func (this *FileStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
|
||||||
return this.openWriter(key, expiresAt, status, -1, -1, false, true)
|
return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileStorage) openWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool, isFlushing bool) (Writer, error) {
|
func (this *FileStorage) openWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool, isFlushing bool) (Writer, error) {
|
||||||
// 是否正在退出
|
// 是否正在退出
|
||||||
if teaconst.IsQuiting {
|
if teaconst.IsQuiting {
|
||||||
return nil, ErrWritingUnavailable
|
return nil, ErrWritingUnavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否已忽略
|
// 是否已忽略
|
||||||
if this.ignoreKeys.Has(key) {
|
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
|
||||||
return nil, ErrEntityTooLarge
|
return nil, ErrEntityTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
// 我们限定仅小文件优先存在内存中
|
// 我们限定仅小文件优先存在内存中
|
||||||
var maxMemorySize = FileToMemoryMaxSize
|
var maxMemorySize = FileToMemoryMaxSize
|
||||||
if maxSize > maxMemorySize {
|
if maxSize > 0 && maxSize < maxMemorySize {
|
||||||
maxMemorySize = maxSize
|
maxMemorySize = maxSize
|
||||||
}
|
}
|
||||||
var memoryStorage = this.memoryStorage
|
var memoryStorage = this.memoryStorage
|
||||||
if !isFlushing && !isPartial && memoryStorage != nil && ((size > 0 && size < maxMemorySize) || size < 0) {
|
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, size, maxMemorySize, false)
|
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return writer, nil
|
return writer, nil
|
||||||
}
|
}
|
||||||
@@ -448,32 +463,17 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 检查是否超出最大值
|
// 检查是否超出容量
|
||||||
count, err := this.list.Count()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys {
|
|
||||||
return nil, NewCapacityError("write file cache failed: too many keys in cache storage")
|
|
||||||
}
|
|
||||||
var capacityBytes = this.diskCapacityBytes()
|
var capacityBytes = this.diskCapacityBytes()
|
||||||
if capacityBytes > 0 && capacityBytes <= this.totalSize {
|
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize() {
|
||||||
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10))
|
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + types.String(this.TotalDiskSize()) + " bytes, capacity: " + types.String(capacityBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = stringutil.Md5(key)
|
var hash = stringutil.Md5(key)
|
||||||
|
|
||||||
// TODO 可以只stat一次
|
dir, diskIsFull := this.subDir(hash)
|
||||||
var dir = this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
if diskIsFull {
|
||||||
_, err = os.Stat(dir)
|
return nil, NewCapacityError("the disk is full")
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = os.MkdirAll(dir, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查缓存是否已经生成
|
// 检查缓存是否已经生成
|
||||||
@@ -520,19 +520,38 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
// 从已经存储的内容中读取信息
|
// 从已经存储的内容中读取信息
|
||||||
var isNewCreated = true
|
var isNewCreated = true
|
||||||
var partialBodyOffset int64
|
var partialBodyOffset int64
|
||||||
|
var partialRanges *PartialRanges
|
||||||
if isPartial {
|
if isPartial {
|
||||||
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
// 数据库中是否存在
|
||||||
if err == nil {
|
existsCacheItem, _ := this.list.Exist(hash)
|
||||||
var partialReader = NewPartialFileReader(readerFp)
|
if existsCacheItem {
|
||||||
err = partialReader.Init()
|
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
||||||
_ = partialReader.Close()
|
if err == nil {
|
||||||
if err == nil && partialReader.bodyOffset > 0 {
|
var partialReader = NewPartialFileReader(readerFp)
|
||||||
isNewCreated = false
|
err = partialReader.Init()
|
||||||
partialBodyOffset = partialReader.bodyOffset
|
_ = partialReader.Close()
|
||||||
} else {
|
if err == nil && partialReader.bodyOffset > 0 {
|
||||||
_ = this.removeCacheFile(tmpPath)
|
partialRanges = partialReader.Ranges()
|
||||||
|
if bodySize > 0 && partialRanges != nil && partialRanges.BodySize > 0 && bodySize != partialRanges.BodySize {
|
||||||
|
_ = this.removeCacheFile(tmpPath)
|
||||||
|
} else {
|
||||||
|
isNewCreated = false
|
||||||
|
partialBodyOffset = partialReader.bodyOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = this.removeCacheFile(tmpPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isNewCreated {
|
||||||
|
err = this.list.Remove(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if partialRanges == nil {
|
||||||
|
partialRanges = NewPartialRanges(expiredAt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = os.O_CREATE | os.O_WRONLY
|
var flags = os.O_CREATE | os.O_WRONLY
|
||||||
@@ -542,7 +561,16 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
var before = time.Now()
|
var before = time.Now()
|
||||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// TODO 检查在各个系统中的稳定性
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
_ = os.MkdirAll(dir, 0777)
|
||||||
|
|
||||||
|
// open file again
|
||||||
|
writer, err = os.OpenFile(tmpPath, flags, 0666)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !isFlushing {
|
if !isFlushing {
|
||||||
if time.Since(before) >= maxOpenFilesSlowCost {
|
if time.Since(before) >= maxOpenFilesSlowCost {
|
||||||
@@ -574,9 +602,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var metaBodySize int64 = -1
|
||||||
|
var metaHeaderSize = -1
|
||||||
if isNewCreated {
|
if isNewCreated {
|
||||||
// 写入过期时间
|
// 写入meta
|
||||||
var metaBytes = make([]byte, SizeMeta+len(key))
|
// 从v0.5.8开始不再在meta中写入Key
|
||||||
|
var metaBytes = make([]byte, SizeMeta)
|
||||||
binary.BigEndian.PutUint32(metaBytes[OffsetExpiresAt:], uint32(expiredAt))
|
binary.BigEndian.PutUint32(metaBytes[OffsetExpiresAt:], uint32(expiredAt))
|
||||||
|
|
||||||
// 写入状态码
|
// 写入状态码
|
||||||
@@ -585,17 +616,17 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
copy(metaBytes[OffsetStatus:], strconv.Itoa(status))
|
copy(metaBytes[OffsetStatus:], strconv.Itoa(status))
|
||||||
|
|
||||||
// 写入URL长度
|
|
||||||
binary.BigEndian.PutUint32(metaBytes[OffsetURLLength:], uint32(len(key)))
|
|
||||||
|
|
||||||
// 写入Header Length
|
// 写入Header Length
|
||||||
binary.BigEndian.PutUint32(metaBytes[OffsetHeaderLength:], uint32(0))
|
if headerSize > 0 {
|
||||||
|
binary.BigEndian.PutUint32(metaBytes[OffsetHeaderLength:], uint32(headerSize))
|
||||||
|
metaHeaderSize = headerSize
|
||||||
|
}
|
||||||
|
|
||||||
// 写入Body Length
|
// 写入Body Length
|
||||||
binary.BigEndian.PutUint64(metaBytes[OffsetBodyLength:], uint64(0))
|
if bodySize > 0 {
|
||||||
|
binary.BigEndian.PutUint64(metaBytes[OffsetBodyLength:], uint64(bodySize))
|
||||||
// 写入URL
|
metaBodySize = bodySize
|
||||||
copy(metaBytes[OffsetKey:], key)
|
}
|
||||||
|
|
||||||
_, err = writer.Write(metaBytes)
|
_, err = writer.Write(metaBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -605,12 +636,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
|
|
||||||
isOk = true
|
isOk = true
|
||||||
if isPartial {
|
if isPartial {
|
||||||
ranges, err := NewPartialRangesFromFile(cachePathName + "@ranges.cache")
|
return NewPartialFileWriter(writer, key, expiredAt, metaHeaderSize, metaBodySize, isNewCreated, isPartial, partialBodyOffset, partialRanges, func() {
|
||||||
if err != nil {
|
|
||||||
ranges = NewPartialRanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewPartialFileWriter(writer, key, expiredAt, isNewCreated, isPartial, partialBodyOffset, ranges, func() {
|
|
||||||
sharedWritingFileKeyLocker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(sharedWritingFileKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
if len(sharedWritingFileKeyMap) == 0 {
|
if len(sharedWritingFileKeyMap) == 0 {
|
||||||
@@ -619,7 +645,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
sharedWritingFileKeyLocker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
}), nil
|
}), nil
|
||||||
} else {
|
} else {
|
||||||
return NewFileWriter(this, writer, key, expiredAt, -1, func() {
|
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, maxSize, func() {
|
||||||
sharedWritingFileKeyLocker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(sharedWritingFileKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
if len(sharedWritingFileKeyMap) == 0 {
|
if len(sharedWritingFileKeyMap) == 0 {
|
||||||
@@ -646,7 +672,7 @@ func (this *FileStorage) AddToList(item *Item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.MetaSize = SizeMeta + 128
|
item.MetaSize = SizeMeta + 128
|
||||||
hash := stringutil.Md5(item.Key)
|
var hash = stringutil.Md5(item.Key)
|
||||||
err := this.list.Add(hash, item)
|
err := this.list.Add(hash, item)
|
||||||
if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||||
remotelogs.Error("CACHE", "add to list failed: "+err.Error())
|
remotelogs.Error("CACHE", "add to list failed: "+err.Error())
|
||||||
@@ -660,15 +686,12 @@ func (this *FileStorage) Delete(key string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
|
||||||
defer this.locker.Unlock()
|
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = memoryStorage.Delete(key)
|
_ = memoryStorage.Delete(key)
|
||||||
})
|
})
|
||||||
|
|
||||||
hash, path := this.keyPath(key)
|
hash, path, _ := this.keyPath(key)
|
||||||
err := this.list.Remove(hash)
|
err := this.list.Remove(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -683,9 +706,6 @@ func (this *FileStorage) Delete(key string) error {
|
|||||||
|
|
||||||
// Stat 统计
|
// Stat 统计
|
||||||
func (this *FileStorage) Stat() (*Stat, error) {
|
func (this *FileStorage) Stat() (*Stat, error) {
|
||||||
this.locker.RLock()
|
|
||||||
defer this.locker.RUnlock()
|
|
||||||
|
|
||||||
return this.list.Stat(func(hash string) bool {
|
return this.list.Stat(func(hash string) bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -708,57 +728,72 @@ func (this *FileStorage) CleanAll() error {
|
|||||||
|
|
||||||
// 删除缓存和目录
|
// 删除缓存和目录
|
||||||
// 不能直接删除子目录,比较危险
|
// 不能直接删除子目录,比较危险
|
||||||
dir := this.dir()
|
|
||||||
fp, err := os.Open(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = fp.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
stat, err := fp.Stat()
|
var rootDirs = []string{this.options.Dir}
|
||||||
if err != nil {
|
var subDirs = this.subDirs // copy slice
|
||||||
return err
|
if len(subDirs) > 0 {
|
||||||
}
|
for _, subDir := range subDirs {
|
||||||
|
rootDirs = append(rootDirs, subDir.Path)
|
||||||
if !stat.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 改成待删除
|
|
||||||
subDirs, err := fp.Readdir(-1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, info := range subDirs {
|
|
||||||
subDir := info.Name()
|
|
||||||
|
|
||||||
// 检查目录名
|
|
||||||
ok, err := regexp.MatchString(`^[0-9a-f]{2}$`, subDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 修改目录名
|
var dirNameReg = regexp.MustCompile(`^[0-9a-f]{2}$`)
|
||||||
tmpDir := dir + "/" + subDir + "-deleted"
|
for _, rootDir := range rootDirs {
|
||||||
err = os.Rename(dir+"/"+subDir, tmpDir)
|
var dir = rootDir + "/p" + types.String(this.policy.Id)
|
||||||
|
err = func(dir string) error {
|
||||||
|
fp, err := os.Open(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stat, err := fp.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改成待删除
|
||||||
|
subDirs, err := fp.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, info := range subDirs {
|
||||||
|
subDir := info.Name()
|
||||||
|
|
||||||
|
// 检查目录名
|
||||||
|
if !dirNameReg.MatchString(subDir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改目录名
|
||||||
|
tmpDir := dir + "/" + subDir + "-deleted"
|
||||||
|
err = os.Rename(dir+"/"+subDir, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新遍历待删除
|
||||||
|
goman.New(func() {
|
||||||
|
err = this.cleanDeletedDirs(dir)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新遍历待删除
|
|
||||||
goman.New(func() {
|
|
||||||
err = this.cleanDeletedDirs(dir)
|
|
||||||
if err != nil {
|
|
||||||
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,9 +804,6 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
|
||||||
defer this.locker.Unlock()
|
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = memoryStorage.Purge(keys, urlType)
|
_ = memoryStorage.Purge(keys, urlType)
|
||||||
@@ -780,6 +812,19 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
// 目录
|
// 目录
|
||||||
if urlType == "dir" {
|
if urlType == "dir" {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchPrefix(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := this.list.CleanPrefix(key)
|
err := this.list.CleanPrefix(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -790,7 +835,21 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
|
|
||||||
// URL
|
// URL
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
hash, path := this.keyPath(key)
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通的Key
|
||||||
|
hash, path, _ := this.keyPath(key)
|
||||||
err := this.removeCacheFile(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
@@ -816,7 +875,10 @@ func (this *FileStorage) Stop() {
|
|||||||
memoryStorage.Stop()
|
memoryStorage.Stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
_ = this.list.Reset()
|
if this.list != nil {
|
||||||
|
_ = this.list.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
if this.purgeTicker != nil {
|
if this.purgeTicker != nil {
|
||||||
this.purgeTicker.Stop()
|
this.purgeTicker.Stop()
|
||||||
}
|
}
|
||||||
@@ -824,7 +886,9 @@ func (this *FileStorage) Stop() {
|
|||||||
this.hotTicker.Stop()
|
this.hotTicker.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = this.list.Close()
|
if this.list != nil {
|
||||||
|
_ = this.list.Close()
|
||||||
|
}
|
||||||
|
|
||||||
var openFileCache = this.openFileCache
|
var openFileCache = this.openFileCache
|
||||||
if openFileCache != nil {
|
if openFileCache != nil {
|
||||||
@@ -836,7 +900,11 @@ func (this *FileStorage) Stop() {
|
|||||||
|
|
||||||
// TotalDiskSize 消耗的磁盘尺寸
|
// TotalDiskSize 消耗的磁盘尺寸
|
||||||
func (this *FileStorage) TotalDiskSize() int64 {
|
func (this *FileStorage) TotalDiskSize() int64 {
|
||||||
return atomic.LoadInt64(&this.totalSize)
|
stat, err := fsutils.StatCache(this.options.Dir)
|
||||||
|
if err == nil {
|
||||||
|
return int64(stat.UsedSize())
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalMemorySize 内存尺寸
|
// TotalMemorySize 内存尺寸
|
||||||
@@ -849,8 +917,8 @@ func (this *FileStorage) TotalMemorySize() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||||
func (this *FileStorage) IgnoreKey(key string) {
|
func (this *FileStorage) IgnoreKey(key string, maxSize int64) {
|
||||||
this.ignoreKeys.Push(key)
|
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanSendfile 是否支持Sendfile
|
// CanSendfile 是否支持Sendfile
|
||||||
@@ -861,25 +929,22 @@ func (this *FileStorage) CanSendfile() bool {
|
|||||||
return this.options.EnableSendfile
|
return this.options.EnableSendfile
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绝对路径
|
|
||||||
func (this *FileStorage) dir() string {
|
|
||||||
return this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取Key对应的文件路径
|
// 获取Key对应的文件路径
|
||||||
func (this *FileStorage) keyPath(key string) (hash string, path string) {
|
func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFull bool) {
|
||||||
hash = stringutil.Md5(key)
|
hash = stringutil.Md5(key)
|
||||||
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
var dir string
|
||||||
|
dir, diskIsFull = this.subDir(hash)
|
||||||
path = dir + "/" + hash + ".cache"
|
path = dir + "/" + hash + ".cache"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Hash对应的文件路径
|
// 获取Hash对应的文件路径
|
||||||
func (this *FileStorage) hashPath(hash string) (path string) {
|
func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
|
||||||
if len(hash) != 32 {
|
if len(hash) != 32 {
|
||||||
return ""
|
return "", false
|
||||||
}
|
}
|
||||||
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
var dir string
|
||||||
|
dir, diskIsFull = this.subDir(hash)
|
||||||
path = dir + "/" + hash + ".cache"
|
path = dir + "/" + hash + ".cache"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -937,19 +1002,39 @@ func (this *FileStorage) initList() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 清理任务
|
// 清理任务
|
||||||
|
// TODO purge每个分区
|
||||||
func (this *FileStorage) purgeLoop() {
|
func (this *FileStorage) purgeLoop() {
|
||||||
|
// 检查磁盘剩余空间
|
||||||
|
this.checkDiskSpace()
|
||||||
|
|
||||||
// 计算是否应该开启LFU清理
|
// 计算是否应该开启LFU清理
|
||||||
var capacityBytes = this.policy.CapacityBytes()
|
var capacityBytes = this.diskCapacityBytes()
|
||||||
var startLFU = false
|
var startLFU = false
|
||||||
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
|
|
||||||
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
||||||
if lfuFreePercent <= 0 {
|
if lfuFreePercent <= 0 {
|
||||||
lfuFreePercent = 5
|
lfuFreePercent = 5
|
||||||
}
|
}
|
||||||
if capacityBytes > 0 {
|
|
||||||
if lfuFreePercent < 100 {
|
var hasFullDisk = this.mainDiskIsFull
|
||||||
if usedPercent >= 100-lfuFreePercent {
|
if !hasFullDisk {
|
||||||
startLFU = true
|
var subDirs = this.subDirs // copy slice
|
||||||
|
for _, subDir := range subDirs {
|
||||||
|
if subDir.IsFull {
|
||||||
|
hasFullDisk = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFullDisk {
|
||||||
|
startLFU = true
|
||||||
|
} else {
|
||||||
|
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
|
||||||
|
if capacityBytes > 0 {
|
||||||
|
if lfuFreePercent < 100 {
|
||||||
|
if usedPercent >= 100-lfuFreePercent {
|
||||||
|
startLFU = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,7 +1059,7 @@ func (this *FileStorage) purgeLoop() {
|
|||||||
}
|
}
|
||||||
for i := 0; i < times; i++ {
|
for i := 0; i < times; i++ {
|
||||||
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
||||||
path := this.hashPath(hash)
|
path, _ := this.hashPath(hash)
|
||||||
err := this.removeCacheFile(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||||
@@ -1008,7 +1093,7 @@ func (this *FileStorage) purgeLoop() {
|
|||||||
|
|
||||||
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
||||||
err := this.list.PurgeLFU(count, func(hash string) error {
|
err := this.list.PurgeLFU(count, func(hash string) error {
|
||||||
path := this.hashPath(hash)
|
path, _ := this.hashPath(hash)
|
||||||
err := this.removeCacheFile(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||||
@@ -1089,7 +1174,7 @@ func (this *FileStorage) hotLoop() {
|
|||||||
expiresAt = bestExpiresAt
|
expiresAt = bestExpiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), reader.BodySize(), -1, false)
|
writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), types.Int(reader.HeaderSize()), reader.BodySize(), -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !CanIgnoreErr(err) {
|
if !CanIgnoreErr(err) {
|
||||||
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
||||||
@@ -1113,9 +1198,12 @@ func (this *FileStorage) hotLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
||||||
_, err = writer.Write(buf[:n])
|
goNext = true
|
||||||
if err == nil {
|
if n > 0 {
|
||||||
goNext = true
|
_, err = writer.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
goNext = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
@@ -1128,6 +1216,7 @@ func (this *FileStorage) hotLoop() {
|
|||||||
memoryStorage.AddToList(&Item{
|
memoryStorage.AddToList(&Item{
|
||||||
Type: writer.ItemType(),
|
Type: writer.ItemType(),
|
||||||
Key: item.Key,
|
Key: item.Key,
|
||||||
|
Host: ParseHost(item.Key),
|
||||||
ExpiredAt: expiresAt,
|
ExpiredAt: expiresAt,
|
||||||
HeaderSize: writer.HeaderSize(),
|
HeaderSize: writer.HeaderSize(),
|
||||||
BodySize: writer.BodySize(),
|
BodySize: writer.BodySize(),
|
||||||
@@ -1255,7 +1344,6 @@ func (this *FileStorage) createMemoryStorage() error {
|
|||||||
Name: this.policy.Name,
|
Name: this.policy.Name,
|
||||||
Description: this.policy.Description,
|
Description: this.policy.Description,
|
||||||
Capacity: this.options.MemoryPolicy.Capacity,
|
Capacity: this.options.MemoryPolicy.Capacity,
|
||||||
MaxKeys: this.policy.MaxKeys,
|
|
||||||
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
||||||
Type: serverconfigs.CachePolicyStorageMemory,
|
Type: serverconfigs.CachePolicyStorageMemory,
|
||||||
Options: this.policy.Options,
|
Options: this.policy.Options,
|
||||||
@@ -1327,3 +1415,59 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
|||||||
f(memoryStorage)
|
f(memoryStorage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查磁盘剩余空间
|
||||||
|
func (this *FileStorage) checkDiskSpace() {
|
||||||
|
if this.options != nil && len(this.options.Dir) > 0 {
|
||||||
|
stat, err := fsutils.Stat(this.options.Dir)
|
||||||
|
if err == nil {
|
||||||
|
this.mainDiskIsFull = stat.FreeSize() < MinDiskSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var subDirs = this.subDirs // copy slice
|
||||||
|
for _, subDir := range subDirs {
|
||||||
|
stat, err := fsutils.Stat(subDir.Path)
|
||||||
|
if err == nil {
|
||||||
|
subDir.IsFull = stat.FreeSize() < MinDiskSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取目录
|
||||||
|
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||||
|
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
|
||||||
|
|
||||||
|
if len(hash) < 4 {
|
||||||
|
return this.options.Dir + suffix, this.mainDiskIsFull
|
||||||
|
}
|
||||||
|
|
||||||
|
var subDirs = this.subDirs // copy slice
|
||||||
|
var countSubDirs = len(subDirs)
|
||||||
|
if countSubDirs == 0 {
|
||||||
|
return this.options.Dir + suffix, this.mainDiskIsFull
|
||||||
|
}
|
||||||
|
|
||||||
|
countSubDirs++ // add main dir
|
||||||
|
|
||||||
|
// 最多只支持16个目录
|
||||||
|
if countSubDirs > 16 {
|
||||||
|
countSubDirs = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirIndex = this.charCode(hash[0]) % uint8(countSubDirs)
|
||||||
|
if dirIndex == 0 {
|
||||||
|
return this.options.Dir + suffix, this.mainDiskIsFull
|
||||||
|
}
|
||||||
|
var subDir = subDirs[dirIndex-1]
|
||||||
|
return subDir.Path + suffix, subDir.IsFull
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileStorage) charCode(r byte) uint8 {
|
||||||
|
if r >= '0' && r <= '9' {
|
||||||
|
return r - '0'
|
||||||
|
}
|
||||||
|
if r >= 'a' && r <= 'z' {
|
||||||
|
return r - 'a' + 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFileStorage_Init(t *testing.T) {
|
func TestFileStorage_Init(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
@@ -26,6 +26,8 @@ func TestFileStorage_Init(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -40,17 +42,22 @@ func TestFileStorage_Init(t *testing.T) {
|
|||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
storage.purgeLoop()
|
storage.purgeLoop()
|
||||||
t.Log(storage.list.(*FileList).total, "entries left")
|
t.Log(storage.list.(*FileList).Stat(func(hash string) bool {
|
||||||
|
return true
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_OpenWriter(t *testing.T) {
|
func TestFileStorage_OpenWriter(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -62,7 +69,7 @@ func TestFileStorage_OpenWriter(t *testing.T) {
|
|||||||
|
|
||||||
header := []byte("Header")
|
header := []byte("Header")
|
||||||
body := []byte("This is Body")
|
body := []byte("This is Body")
|
||||||
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -95,12 +102,15 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
|||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, true)
|
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, -1, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -123,13 +133,16 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -139,7 +152,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
|||||||
t.Log(time.Since(now).Seconds()*1000, "ms")
|
t.Log(time.Since(now).Seconds()*1000, "ms")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -188,13 +201,16 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -212,7 +228,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
|||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrFileIsWriting {
|
if err != ErrFileIsWriting {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -243,13 +259,16 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -267,7 +286,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
|||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrFileIsWriting {
|
if err != ErrFileIsWriting {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -299,13 +318,16 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Read(t *testing.T) {
|
func TestFileStorage_Read(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -335,13 +357,16 @@ func TestFileStorage_Read(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -388,13 +413,16 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Read_NotFound(t *testing.T) {
|
func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -421,13 +449,16 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Delete(t *testing.T) {
|
func TestFileStorage_Delete(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -440,13 +471,16 @@ func TestFileStorage_Delete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Stat(t *testing.T) {
|
func TestFileStorage_Stat(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -465,13 +499,16 @@ func TestFileStorage_Stat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_CleanAll(t *testing.T) {
|
func TestFileStorage_CleanAll(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -496,13 +533,16 @@ func TestFileStorage_CleanAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_Stop(t *testing.T) {
|
func TestFileStorage_Stop(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -518,16 +558,22 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
|||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, path := storage.keyPath("my-key")
|
_, path, _ := storage.keyPath("my-key")
|
||||||
t.Log(path)
|
t.Log(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||||
var storage = NewFileStorage(nil)
|
var storage = NewFileStorage(nil)
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,13 +582,16 @@ func BenchmarkFileStorage_Read(b *testing.B) {
|
|||||||
|
|
||||||
_ = utils.SetRLimit(1024 * 1024)
|
_ = utils.SetRLimit(1024 * 1024)
|
||||||
|
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
"dir": Tea.Root + "/caches",
|
"dir": Tea.Root + "/caches",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer storage.Stop()
|
||||||
|
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
@@ -569,6 +618,6 @@ func BenchmarkFileStorage_KeyPath(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = storage.keyPath(strconv.Itoa(i))
|
_, _, _ = storage.keyPath(strconv.Itoa(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ type StorageInterface interface {
|
|||||||
|
|
||||||
// OpenWriter 打开缓存写入器等待写入
|
// OpenWriter 打开缓存写入器等待写入
|
||||||
// size 和 maxSize 可能为-1
|
// size 和 maxSize 可能为-1
|
||||||
OpenWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error)
|
OpenWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error)
|
||||||
|
|
||||||
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
||||||
OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error)
|
OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error)
|
||||||
|
|
||||||
// Delete 删除某个键值对应的缓存
|
// Delete 删除某个键值对应的缓存
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
@@ -54,7 +54,7 @@ type StorageInterface interface {
|
|||||||
AddToList(item *Item)
|
AddToList(item *Item)
|
||||||
|
|
||||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||||
IgnoreKey(key string)
|
IgnoreKey(key string, maxSize int64)
|
||||||
|
|
||||||
// CanSendfile 是否支持Sendfile
|
// CanSendfile 是否支持Sendfile
|
||||||
CanSendfile() bool
|
CanSendfile() bool
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"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/utils/fasttime"
|
||||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -32,7 +33,7 @@ type MemoryItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *MemoryItem) IsExpired() bool {
|
func (this *MemoryItem) IsExpired() bool {
|
||||||
return this.ExpiresAt < utils.UnixTime()
|
return this.ExpiresAt < fasttime.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryStorage struct {
|
type MemoryStorage struct {
|
||||||
@@ -110,8 +111,15 @@ func (this *MemoryStorage) Init() error {
|
|||||||
|
|
||||||
// OpenReader 读取缓存
|
// OpenReader 读取缓存
|
||||||
func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
|
func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
|
||||||
hash := this.hash(key)
|
var hash = this.hash(key)
|
||||||
|
|
||||||
|
// check if exists in list
|
||||||
|
exists, _ := this.list.Exist(types.String(hash))
|
||||||
|
if !exists {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// read from valuesMap
|
||||||
this.locker.RLock()
|
this.locker.RLock()
|
||||||
item := this.valuesMap[hash]
|
item := this.valuesMap[hash]
|
||||||
if item == nil || !item.IsDone {
|
if item == nil || !item.IsDone {
|
||||||
@@ -119,8 +127,8 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
|||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if useStale || (item.ExpiresAt > utils.UnixTime()) {
|
if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
|
||||||
reader := NewMemoryReader(item)
|
var reader = NewMemoryReader(item)
|
||||||
err := reader.Init()
|
err := reader.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.locker.RUnlock()
|
this.locker.RUnlock()
|
||||||
@@ -149,8 +157,8 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenWriter 打开缓存写入器等待写入
|
// OpenWriter 打开缓存写入器等待写入
|
||||||
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) {
|
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||||
if this.ignoreKeys.Has(key) {
|
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
|
||||||
return nil, ErrEntityTooLarge
|
return nil, ErrEntityTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,15 +166,15 @@ func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, s
|
|||||||
if isPartial {
|
if isPartial {
|
||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
return this.openWriter(key, expiredAt, status, size, maxSize, true)
|
return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
||||||
func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) {
|
func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
|
||||||
return this.openWriter(key, expiresAt, status, -1, -1, true)
|
return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isDirty bool) (Writer, error) {
|
func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isDirty bool) (Writer, error) {
|
||||||
// 待写入队列是否已满
|
// 待写入队列是否已满
|
||||||
if isDirty &&
|
if isDirty &&
|
||||||
this.parentStorage != nil &&
|
this.parentStorage != nil &&
|
||||||
@@ -192,30 +200,32 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, s
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// 检查是否过期
|
// 检查是否过期
|
||||||
hash := this.hash(key)
|
var hash = this.hash(key)
|
||||||
item, ok := this.valuesMap[hash]
|
item, ok := this.valuesMap[hash]
|
||||||
if ok && !item.IsExpired() {
|
if ok && !item.IsExpired() {
|
||||||
return nil, ErrFileIsWriting
|
var hashString = types.String(hash)
|
||||||
|
exists, _ := this.list.Exist(hashString)
|
||||||
|
if !exists {
|
||||||
|
// remove from values map
|
||||||
|
delete(this.valuesMap, hash)
|
||||||
|
_ = this.list.Remove(hashString)
|
||||||
|
item = nil
|
||||||
|
} else {
|
||||||
|
return nil, ErrFileIsWriting
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否超出最大值
|
// 检查是否超出最大值
|
||||||
totalKeys, err := this.list.Count()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if this.policy.MaxKeys > 0 && totalKeys > this.policy.MaxKeys {
|
|
||||||
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
|
|
||||||
}
|
|
||||||
capacityBytes := this.memoryCapacityBytes()
|
capacityBytes := this.memoryCapacityBytes()
|
||||||
if size < 0 {
|
if bodySize < 0 {
|
||||||
size = 0
|
bodySize = 0
|
||||||
}
|
}
|
||||||
if capacityBytes > 0 && capacityBytes <= this.totalSize+size {
|
if capacityBytes > 0 && capacityBytes <= this.totalSize+bodySize {
|
||||||
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
|
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先删除
|
// 先删除
|
||||||
err = this.deleteWithoutLocker(key)
|
err := this.deleteWithoutLocker(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -230,10 +240,10 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, s
|
|||||||
|
|
||||||
// Delete 删除某个键值对应的缓存
|
// Delete 删除某个键值对应的缓存
|
||||||
func (this *MemoryStorage) Delete(key string) error {
|
func (this *MemoryStorage) Delete(key string) error {
|
||||||
hash := this.hash(key)
|
var hash = this.hash(key)
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
delete(this.valuesMap, hash)
|
delete(this.valuesMap, hash)
|
||||||
_ = this.list.Remove(fmt.Sprintf("%d", hash))
|
_ = this.list.Remove(types.String(hash))
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -263,6 +273,19 @@ func (this *MemoryStorage) Purge(keys []string, urlType string) error {
|
|||||||
// 目录
|
// 目录
|
||||||
if urlType == "dir" {
|
if urlType == "dir" {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchPrefix(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := this.list.CleanPrefix(key)
|
err := this.list.CleanPrefix(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -273,6 +296,19 @@ func (this *MemoryStorage) Purge(keys []string, urlType string) error {
|
|||||||
|
|
||||||
// URL
|
// URL
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := this.Delete(key)
|
err := this.Delete(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -326,6 +362,9 @@ func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy
|
|||||||
if newPolicy.CapacityBytes() == 0 {
|
if newPolicy.CapacityBytes() == 0 {
|
||||||
_ = this.CleanAll()
|
_ = this.CleanAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset ignored keys
|
||||||
|
this.ignoreKeys.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanUpdatePolicy 检查策略是否可以更新
|
// CanUpdatePolicy 检查策略是否可以更新
|
||||||
@@ -336,7 +375,12 @@ func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePol
|
|||||||
// AddToList 将缓存添加到列表
|
// AddToList 将缓存添加到列表
|
||||||
func (this *MemoryStorage) AddToList(item *Item) {
|
func (this *MemoryStorage) AddToList(item *Item) {
|
||||||
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
||||||
hash := fmt.Sprintf("%d", this.hash(item.Key))
|
var hash = types.String(this.hash(item.Key))
|
||||||
|
|
||||||
|
if len(item.Host) == 0 {
|
||||||
|
item.Host = ParseHost(item.Key)
|
||||||
|
}
|
||||||
|
|
||||||
_ = this.list.Add(hash, item)
|
_ = this.list.Add(hash, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,8 +395,8 @@ func (this *MemoryStorage) TotalMemorySize() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||||
func (this *MemoryStorage) IgnoreKey(key string) {
|
func (this *MemoryStorage) IgnoreKey(key string, maxSize int64) {
|
||||||
this.ignoreKeys.Push(key)
|
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanSendfile 是否支持Sendfile
|
// CanSendfile 是否支持Sendfile
|
||||||
@@ -433,7 +477,7 @@ func (this *MemoryStorage) startFlush() {
|
|||||||
var statCount = 0
|
var statCount = 0
|
||||||
var writeDelayMS float64 = 0
|
var writeDelayMS float64 = 0
|
||||||
|
|
||||||
for hash := range this.dirtyChan {
|
for key := range this.dirtyChan {
|
||||||
statCount++
|
statCount++
|
||||||
|
|
||||||
if statCount == 100 {
|
if statCount == 100 {
|
||||||
@@ -455,7 +499,7 @@ func (this *MemoryStorage) startFlush() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.flushItem(hash)
|
this.flushItem(key)
|
||||||
|
|
||||||
if writeDelayMS > 0 {
|
if writeDelayMS > 0 {
|
||||||
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
||||||
@@ -477,11 +521,15 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !item.IsDone || item.IsExpired() {
|
if !item.IsDone {
|
||||||
|
remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if item.IsExpired() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status)
|
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !CanIgnoreErr(err) {
|
if !CanIgnoreErr(err) {
|
||||||
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
||||||
@@ -513,6 +561,7 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
this.parentStorage.AddToList(&Item{
|
this.parentStorage.AddToList(&Item{
|
||||||
Type: writer.ItemType(),
|
Type: writer.ItemType(),
|
||||||
Key: key,
|
Key: key,
|
||||||
|
Host: ParseHost(key),
|
||||||
ExpiredAt: item.ExpiresAt,
|
ExpiredAt: item.ExpiresAt,
|
||||||
HeaderSize: writer.HeaderSize(),
|
HeaderSize: writer.HeaderSize(),
|
||||||
BodySize: writer.BodySize(),
|
BodySize: writer.BodySize(),
|
||||||
@@ -520,8 +569,6 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
|
|
||||||
// 从内存中移除
|
// 从内存中移除
|
||||||
_ = this.Delete(key)
|
_ = this.Delete(key)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
||||||
@@ -544,7 +591,7 @@ func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
|||||||
func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
||||||
hash := this.hash(key)
|
hash := this.hash(key)
|
||||||
delete(this.valuesMap, hash)
|
delete(this.valuesMap, hash)
|
||||||
_ = this.list.Remove(fmt.Sprintf("%d", hash))
|
_ = this.list.Remove(types.String(hash))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,15 +14,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.WriteHeader([]byte("Header"))
|
_, _ = writer.WriteHeader([]byte("Header"))
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
_, _ = writer.Write([]byte(", World"))
|
_, _ = writer.Write([]byte(", World"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(storage.valuesMap)
|
t.Log(storage.valuesMap)
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -30,6 +37,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc")
|
t.Log("not found: abc")
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -63,7 +71,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -102,21 +110,29 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Delete(t *testing.T) {
|
func TestMemoryStorage_Delete(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
}
|
}
|
||||||
_ = storage.Delete("abc1")
|
_ = storage.Delete("abc1")
|
||||||
@@ -124,14 +140,18 @@ func TestMemoryStorage_Delete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Stat(t *testing.T) {
|
func TestMemoryStorage_Stat(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc",
|
Key: "abc",
|
||||||
@@ -140,11 +160,15 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc1",
|
Key: "abc1",
|
||||||
@@ -161,14 +185,18 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_CleanAll(t *testing.T) {
|
func TestMemoryStorage_CleanAll(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
var expiredAt = time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc",
|
Key: "abc",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -176,11 +204,15 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc1",
|
Key: "abc1",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -199,11 +231,15 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc",
|
Key: "abc",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -211,11 +247,15 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc1",
|
Key: "abc1",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -231,7 +271,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Expire(t *testing.T) {
|
func TestMemoryStorage_Expire(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
MemoryAutoPurgeInterval: 5,
|
MemoryAutoPurgeInterval: 5,
|
||||||
}, nil)
|
}, nil)
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
@@ -242,11 +282,15 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
|||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
|
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
|
||||||
key := "abc" + strconv.Itoa(i)
|
key := "abc" + strconv.Itoa(i)
|
||||||
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: key,
|
Key: key,
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -257,7 +301,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Locker(t *testing.T) {
|
func TestMemoryStorage_Locker(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
30
internal/caches/utils.go
Normal file
30
internal/caches/utils.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseHost(key string) string {
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstSlashIndex = strings.Index(key[schemeIndex+3:], "/")
|
||||||
|
if firstSlashIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = key[schemeIndex+3 : schemeIndex+3+firstSlashIndex]
|
||||||
|
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = configutils.QuoteIP(hostPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
return host
|
||||||
|
}
|
||||||
51
internal/caches/utils_test.go
Normal file
51
internal/caches/utils_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/cespare/xxhash"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseHost(t *testing.T) {
|
||||||
|
for _, u := range []string{
|
||||||
|
"https://goedge.cn/hello/world",
|
||||||
|
"https://goedge.cn:8080/hello/world",
|
||||||
|
"https://goedge.cn/hello/world?v=1&t=123",
|
||||||
|
"https://[::1]:1234/hello/world?v=1&t=123",
|
||||||
|
"https://[::1]/hello/world?v=1&t=123",
|
||||||
|
"https://127.0.0.1/hello/world?v=1&t=123",
|
||||||
|
"https:/hello/world?v=1&t=123",
|
||||||
|
"123456",
|
||||||
|
} {
|
||||||
|
t.Log(u, "=>", caches.ParseHost(u))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUintString(t *testing.T) {
|
||||||
|
t.Log(strconv.FormatUint(xxhash.Sum64String("https://goedge.cn/"), 10))
|
||||||
|
t.Log(strconv.FormatUint(123456789, 10))
|
||||||
|
t.Log(fmt.Sprintf("%d", 1234567890123))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUint_String(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = strconv.FormatUint(1234567890123, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUint_String2(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = types.String(1234567890123)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUint_String3(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = fmt.Sprintf("%d", 1234567890123)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,25 +11,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FileWriter struct {
|
type FileWriter struct {
|
||||||
storage StorageInterface
|
storage StorageInterface
|
||||||
rawWriter *os.File
|
rawWriter *os.File
|
||||||
key string
|
key string
|
||||||
headerSize int64
|
|
||||||
bodySize int64
|
metaHeaderSize int
|
||||||
expiredAt int64
|
headerSize int64
|
||||||
maxSize int64
|
|
||||||
endFunc func()
|
metaBodySize int64 // 写入前的内容长度
|
||||||
once sync.Once
|
bodySize int64
|
||||||
|
|
||||||
|
expiredAt int64
|
||||||
|
maxSize int64
|
||||||
|
endFunc func()
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, maxSize int64, endFunc func()) *FileWriter {
|
func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, metaHeaderSize int, metaBodySize int64, maxSize int64, endFunc func()) *FileWriter {
|
||||||
return &FileWriter{
|
return &FileWriter{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
key: key,
|
key: key,
|
||||||
rawWriter: rawWriter,
|
rawWriter: rawWriter,
|
||||||
expiredAt: expiredAt,
|
expiredAt: expiredAt,
|
||||||
maxSize: maxSize,
|
maxSize: maxSize,
|
||||||
endFunc: endFunc,
|
endFunc: endFunc,
|
||||||
|
metaHeaderSize: metaHeaderSize,
|
||||||
|
metaBodySize: metaBodySize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +52,10 @@ func (this *FileWriter) WriteHeader(data []byte) (n int, err error) {
|
|||||||
|
|
||||||
// WriteHeaderLength 写入Header长度数据
|
// WriteHeaderLength 写入Header长度数据
|
||||||
func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
||||||
bytes4 := make([]byte, 4)
|
if this.metaHeaderSize > 0 && this.metaHeaderSize == headerLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes4 = make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,7 +79,7 @@ func (this *FileWriter) Write(data []byte) (n int, err error) {
|
|||||||
err = ErrEntityTooLarge
|
err = ErrEntityTooLarge
|
||||||
|
|
||||||
if this.storage != nil {
|
if this.storage != nil {
|
||||||
this.storage.IgnoreKey(this.key)
|
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +98,10 @@ func (this *FileWriter) WriteAt(offset int64, data []byte) error {
|
|||||||
|
|
||||||
// WriteBodyLength 写入Body长度数据
|
// WriteBodyLength 写入Body长度数据
|
||||||
func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
|
func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
|
||||||
bytes8 := make([]byte, 8)
|
if this.metaBodySize >= 0 && bodyLength == this.metaBodySize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes8 = make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,7 +122,7 @@ func (this *FileWriter) Close() error {
|
|||||||
this.endFunc()
|
this.endFunc()
|
||||||
})
|
})
|
||||||
|
|
||||||
path := this.rawWriter.Name()
|
var path = this.rawWriter.Name()
|
||||||
|
|
||||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (this *MemoryWriter) Write(data []byte) (n int, err error) {
|
|||||||
// 检查尺寸
|
// 检查尺寸
|
||||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||||
err = ErrEntityTooLarge
|
err = ErrEntityTooLarge
|
||||||
this.storage.IgnoreKey(this.key)
|
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||||
return len(data), err
|
return len(data), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PartialFileWriter struct {
|
type PartialFileWriter struct {
|
||||||
rawWriter *os.File
|
rawWriter *os.File
|
||||||
key string
|
key string
|
||||||
headerSize int64
|
|
||||||
bodySize int64
|
metaHeaderSize int
|
||||||
expiredAt int64
|
headerSize int64
|
||||||
endFunc func()
|
|
||||||
once sync.Once
|
metaBodySize int64
|
||||||
|
bodySize int64
|
||||||
|
|
||||||
|
expiredAt int64
|
||||||
|
endFunc func()
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
isNew bool
|
isNew bool
|
||||||
isPartial bool
|
isPartial bool
|
||||||
@@ -27,17 +32,19 @@ type PartialFileWriter struct {
|
|||||||
rangePath string
|
rangePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter {
|
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaHeaderSize int, metaBodySize int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter {
|
||||||
return &PartialFileWriter{
|
return &PartialFileWriter{
|
||||||
key: key,
|
key: key,
|
||||||
rawWriter: rawWriter,
|
rawWriter: rawWriter,
|
||||||
expiredAt: expiredAt,
|
expiredAt: expiredAt,
|
||||||
endFunc: endFunc,
|
endFunc: endFunc,
|
||||||
isNew: isNew,
|
isNew: isNew,
|
||||||
isPartial: isPartial,
|
isPartial: isPartial,
|
||||||
bodyOffset: bodyOffset,
|
bodyOffset: bodyOffset,
|
||||||
ranges: ranges,
|
ranges: ranges,
|
||||||
rangePath: partialRangesFilePath(rawWriter.Name()),
|
rangePath: partialRangesFilePath(rawWriter.Name()),
|
||||||
|
metaHeaderSize: metaHeaderSize,
|
||||||
|
metaBodySize: metaBodySize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +78,11 @@ func (this *PartialFileWriter) AppendHeader(data []byte) error {
|
|||||||
|
|
||||||
// WriteHeaderLength 写入Header长度数据
|
// WriteHeaderLength 写入Header长度数据
|
||||||
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
||||||
bytes4 := make([]byte, 4)
|
if this.metaHeaderSize > 0 && this.metaHeaderSize == headerLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes4 = make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,8 +121,13 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if this.bodyOffset == 0 {
|
if this.bodyOffset == 0 {
|
||||||
this.bodyOffset = SizeMeta + int64(len(this.key)) + this.headerSize
|
var keyLength = 0
|
||||||
|
if this.ranges.Version == 0 { // 以往的版本包含有Key
|
||||||
|
keyLength = len(this.key)
|
||||||
|
}
|
||||||
|
this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -129,7 +145,10 @@ func (this *PartialFileWriter) SetBodyLength(bodyLength int64) {
|
|||||||
|
|
||||||
// WriteBodyLength 写入Body长度数据
|
// WriteBodyLength 写入Body长度数据
|
||||||
func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
|
func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
|
||||||
bytes8 := make([]byte, 8)
|
if this.metaBodySize > 0 && this.metaBodySize == bodyLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes8 = make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -150,8 +169,11 @@ func (this *PartialFileWriter) Close() error {
|
|||||||
this.endFunc()
|
this.endFunc()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.ranges.BodySize = this.bodySize
|
||||||
err := this.ranges.WriteToFile(this.rangePath)
|
err := this.ranges.WriteToFile(this.rangePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = this.rawWriter.Close()
|
||||||
|
this.remove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func TestPartialFileWriter_Write(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var ranges = caches.NewPartialRanges()
|
var ranges = caches.NewPartialRanges(0)
|
||||||
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, true, true, 0, ranges, func() {
|
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, -1, -1, true, true, 0, ranges, func() {
|
||||||
t.Log("end")
|
t.Log("end")
|
||||||
})
|
})
|
||||||
_, err = writer.WriteHeader([]byte("header"))
|
_, err = writer.WriteHeader([]byte("header"))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
var sharedBrotliReaderPool *ReaderPool
|
var sharedBrotliReaderPool *ReaderPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
var sharedDeflateReaderPool *ReaderPool
|
var sharedDeflateReaderPool *ReaderPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
var sharedGzipReaderPool *ReaderPool
|
var sharedGzipReaderPool *ReaderPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
var sharedZSTDReaderPool *ReaderPool
|
var sharedZSTDReaderPool *ReaderPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
var sharedBrotliWriterPool *WriterPool
|
var sharedBrotliWriterPool *WriterPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
var sharedDeflateWriterPool *WriterPool
|
var sharedDeflateWriterPool *WriterPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
var sharedGzipWriterPool *WriterPool
|
var sharedGzipWriterPool *WriterPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
var sharedZSTDWriterPool *WriterPool
|
var sharedZSTDWriterPool *WriterPool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import (
|
|||||||
// APIConfig 节点API配置
|
// APIConfig 节点API配置
|
||||||
type APIConfig struct {
|
type APIConfig struct {
|
||||||
RPC struct {
|
RPC struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||||
DisableUpdate bool `yaml:"disableUpdate"`
|
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||||
} `yaml:"rpc"`
|
} `yaml:"rpc" json:"rpc"`
|
||||||
NodeId string `yaml:"nodeId"`
|
NodeId string `yaml:"nodeId" json:"nodeId"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret" json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIConfig() *APIConfig {
|
||||||
|
return &APIConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadAPIConfig() (*APIConfig, error) {
|
func LoadAPIConfig() (*APIConfig, error) {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package configs
|
|||||||
// ClusterConfig 集群配置
|
// ClusterConfig 集群配置
|
||||||
type ClusterConfig struct {
|
type ClusterConfig struct {
|
||||||
RPC struct {
|
RPC struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||||
DisableUpdate bool `yaml:"disableUpdate"`
|
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||||
} `yaml:"rpc"`
|
} `yaml:"rpc" json:"rpc"`
|
||||||
ClusterId string `yaml:"clusterId"`
|
ClusterId string `yaml:"clusterId" json:"clusterId"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret" json:"secret"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package configs
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
var sharedLocker = &sync.RWMutex{}
|
|
||||||
7
internal/conns/linger.go
Normal file
7
internal/conns/linger.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package conns
|
||||||
|
|
||||||
|
type LingerConn interface {
|
||||||
|
SetLinger(sec int) error
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
package conns
|
package conns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -10,14 +11,14 @@ import (
|
|||||||
var SharedMap = NewMap()
|
var SharedMap = NewMap()
|
||||||
|
|
||||||
type Map struct {
|
type Map struct {
|
||||||
m map[string]map[int]net.Conn // ip => { port => Conn }
|
m map[string]map[string]net.Conn // ip => { network_port => Conn }
|
||||||
|
|
||||||
locker sync.RWMutex
|
locker sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMap() *Map {
|
func NewMap() *Map {
|
||||||
return &Map{
|
return &Map{
|
||||||
m: map[string]map[int]net.Conn{},
|
m: map[string]map[string]net.Conn{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,23 +26,19 @@ func (this *Map) Add(conn net.Conn) {
|
|||||||
if conn == nil {
|
if conn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
|
||||||
|
key, ip, ok := this.connAddr(conn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ip = tcpAddr.IP.String()
|
|
||||||
var port = tcpAddr.Port
|
|
||||||
|
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
connMap, ok := this.m[ip]
|
connMap, ok := this.m[ip]
|
||||||
if !ok {
|
if !ok {
|
||||||
this.m[ip] = map[int]net.Conn{
|
this.m[ip] = map[string]net.Conn{key: conn}
|
||||||
port: conn,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
connMap[port] = conn
|
connMap[key] = conn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,14 +46,11 @@ func (this *Map) Remove(conn net.Conn) {
|
|||||||
if conn == nil {
|
if conn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
key, ip, ok := this.connAddr(conn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ip = tcpAddr.IP.String()
|
|
||||||
var port = tcpAddr.Port
|
|
||||||
|
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
@@ -64,7 +58,7 @@ func (this *Map) Remove(conn net.Conn) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(connMap, port)
|
delete(connMap, key)
|
||||||
|
|
||||||
if len(connMap) == 0 {
|
if len(connMap) == 0 {
|
||||||
delete(this.m, ip)
|
delete(this.m, ip)
|
||||||
@@ -96,6 +90,13 @@ func (this *Map) CloseIPConns(ip string) {
|
|||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
for _, conn := range conns {
|
for _, conn := range conns {
|
||||||
|
// 设置Linger
|
||||||
|
lingerConn, isLingerConn := conn.(LingerConn)
|
||||||
|
if isLingerConn {
|
||||||
|
_ = lingerConn.SetLinger(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,9 +110,31 @@ func (this *Map) AllConns() []net.Conn {
|
|||||||
|
|
||||||
var result = []net.Conn{}
|
var result = []net.Conn{}
|
||||||
for _, m := range this.m {
|
for _, m := range this.m {
|
||||||
for _, conn := range m {
|
for _, connInfo := range m {
|
||||||
result = append(result, conn)
|
result = append(result, connInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Map) connAddr(conn net.Conn) (key string, ip string, ok bool) {
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr = conn.RemoteAddr()
|
||||||
|
switch realAddr := addr.(type) {
|
||||||
|
case *net.TCPAddr:
|
||||||
|
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
|
||||||
|
case *net.UDPAddr:
|
||||||
|
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
|
||||||
|
default:
|
||||||
|
var s = addr.String()
|
||||||
|
host, port, err := net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return addr.Network() + port, host, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.5.2"
|
Version = "1.2.2"
|
||||||
|
|
||||||
ProductName = "Edge Node"
|
ProductName = "Edge Node"
|
||||||
ProcessName = "edge-node"
|
ProcessName = "edge-node"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package teaconst
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -15,7 +16,7 @@ var (
|
|||||||
|
|
||||||
NodeId int64 = 0
|
NodeId int64 = 0
|
||||||
NodeIdString = ""
|
NodeIdString = ""
|
||||||
IsDaemon = len(os.Args) > 1 && os.Args[1] == "daemon"
|
IsMain = checkMain()
|
||||||
|
|
||||||
GlobalProductName = nodeconfigs.DefaultProductName
|
GlobalProductName = nodeconfigs.DefaultProductName
|
||||||
|
|
||||||
@@ -24,3 +25,15 @@ var (
|
|||||||
|
|
||||||
DiskIsFast = false // 是否为高速硬盘
|
DiskIsFast = false // 是否为高速硬盘
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 检查是否为主程序
|
||||||
|
func checkMain() bool {
|
||||||
|
if len(os.Args) == 1 ||
|
||||||
|
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
return strings.HasSuffix(exe, ".test") ||
|
||||||
|
strings.HasSuffix(exe, ".test.exe") ||
|
||||||
|
strings.Contains(exe, "___")
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ func On(event Event, callback func()) {
|
|||||||
OnKey(event, nil, callback)
|
OnKey(event, nil, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OnClose(callback func()) {
|
||||||
|
On(EventQuit, callback)
|
||||||
|
On(EventTerminated, callback)
|
||||||
|
}
|
||||||
|
|
||||||
// OnKey 使用Key增加事件回调
|
// OnKey 使用Key增加事件回调
|
||||||
func OnKey(event Event, key interface{}, callback func()) {
|
func OnKey(event Event, key interface{}, callback func()) {
|
||||||
if key == nil {
|
if key == nil {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package firewalls
|
package firewalls
|
||||||
|
|
||||||
@@ -10,22 +9,29 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
if !teaconst.IsMain {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
events.On(events.EventReload, func() {
|
events.On(events.EventReload, func() {
|
||||||
if nftablesInstance == nil {
|
if nftablesInstance == nil {
|
||||||
return
|
return
|
||||||
@@ -53,29 +59,31 @@ func init() {
|
|||||||
|
|
||||||
// DDoSProtectionManager DDoS防护
|
// DDoSProtectionManager DDoS防护
|
||||||
type DDoSProtectionManager struct {
|
type DDoSProtectionManager struct {
|
||||||
nftPath string
|
|
||||||
|
|
||||||
lastAllowIPList []string
|
lastAllowIPList []string
|
||||||
lastConfig []byte
|
lastConfig []byte
|
||||||
|
|
||||||
|
locker sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDDoSProtectionManager 获取新对象
|
// NewDDoSProtectionManager 获取新对象
|
||||||
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
||||||
nftPath, _ := exec.LookPath("nft")
|
return &DDoSProtectionManager{}
|
||||||
|
|
||||||
return &DDoSProtectionManager{
|
|
||||||
nftPath: nftPath,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply 应用配置
|
// Apply 应用配置
|
||||||
func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) error {
|
func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) error {
|
||||||
|
// 加锁防止并发更改
|
||||||
|
if !this.locker.TryLock() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
// 同集群节点IP白名单
|
// 同集群节点IP白名单
|
||||||
var allowIPListChanged = false
|
var allowIPListChanged = false
|
||||||
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||||
if nodeConfig != nil {
|
if nodeConfig != nil {
|
||||||
var allowIPList = nodeConfig.AllowedIPs
|
var allowIPList = nodeConfig.AllowedIPs
|
||||||
if !utils.ContainsSameStrings(allowIPList, this.lastAllowIPList) {
|
if !utils.EqualStrings(allowIPList, this.lastAllowIPList) {
|
||||||
allowIPListChanged = true
|
allowIPListChanged = true
|
||||||
this.lastAllowIPList = allowIPList
|
this.lastAllowIPList = allowIPList
|
||||||
}
|
}
|
||||||
@@ -91,11 +99,14 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
|||||||
}
|
}
|
||||||
remotelogs.Println("FIREWALL", "change DDoS protection config")
|
remotelogs.Println("FIREWALL", "change DDoS protection config")
|
||||||
|
|
||||||
if len(this.nftPath) == 0 {
|
if len(nftables.NftExePath()) == 0 {
|
||||||
return errors.New("can not find nft command")
|
return errors.New("can not find nft command")
|
||||||
}
|
}
|
||||||
|
|
||||||
if nftablesInstance == nil {
|
if nftablesInstance == nil {
|
||||||
|
if config == nil || !config.IsOn() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return errors.New("nftables instance should not be nil")
|
return errors.New("nftables instance should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +165,11 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
|||||||
|
|
||||||
// 添加TCP规则
|
// 添加TCP规则
|
||||||
func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error {
|
func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error {
|
||||||
|
var nftExe = nftables.NftExePath()
|
||||||
|
if len(nftExe) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 检查nft版本不能小于0.9
|
// 检查nft版本不能小于0.9
|
||||||
if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 {
|
if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -263,23 +279,21 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
|||||||
// 添加新规则
|
// 添加新规则
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
if maxConnections > 0 {
|
if maxConnections > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 让用户选择是drop还是reject
|
// TODO 让用户选择是drop还是reject
|
||||||
if maxConnectionsPerIP > 0 {
|
if maxConnectionsPerIP > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,20 +301,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
|||||||
// TODO 让用户选择是drop还是reject
|
// TODO 让用户选择是drop还是reject
|
||||||
if newConnectionsMinutelyRate > 0 {
|
if newConnectionsMinutelyRate > 0 {
|
||||||
if newConnectionsMinutelyRateBlockTimeout > 0 {
|
if newConnectionsMinutelyRateBlockTimeout > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,20 +321,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
|||||||
// TODO 让用户选择是drop还是reject
|
// TODO 让用户选择是drop还是reject
|
||||||
if newConnectionsSecondlyRate > 0 {
|
if newConnectionsSecondlyRate > 0 {
|
||||||
if newConnectionsSecondlyRateBlockTimeout > 0 {
|
if newConnectionsSecondlyRateBlockTimeout > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,7 +554,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
|||||||
_, ok := oldMap[ip]
|
_, ok := oldMap[ip]
|
||||||
if !ok {
|
if !ok {
|
||||||
// 不存在则添加
|
// 不存在则添加
|
||||||
err = set.AddIPElement(ip, nil)
|
err = set.AddIPElement(ip, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add ip '" + ip + "' failed: " + err.Error())
|
return errors.New("add ip '" + ip + "' failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
||||||
|
|
||||||
type DDoSProtectionManager struct {
|
type DDoSProtectionManager struct {
|
||||||
nftPath string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package firewalls
|
package firewalls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -14,6 +15,10 @@ var firewallLocker = &sync.Mutex{}
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
func init() {
|
func init() {
|
||||||
|
if !teaconst.IsMain {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
events.On(events.EventLoaded, func() {
|
events.On(events.EventLoaded, func() {
|
||||||
var firewall = Firewall()
|
var firewall = Firewall()
|
||||||
if firewall.Name() != "mock" {
|
if firewall.Name() != "mock" {
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type firewalldCmd struct {
|
type firewalldCmd struct {
|
||||||
cmd *exec.Cmd
|
cmd *executils.Cmd
|
||||||
denyIP string
|
denyIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,9 +31,9 @@ func NewFirewalld() *Firewalld {
|
|||||||
cmdQueue: make(chan *firewalldCmd, 4096),
|
cmdQueue: make(chan *firewalldCmd, 4096),
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath("firewall-cmd")
|
path, err := executils.LookPath("firewall-cmd")
|
||||||
if err == nil && len(path) > 0 {
|
if err == nil && len(path) > 0 {
|
||||||
var cmd = exec.Command(path, "--state")
|
var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
firewalld.exe = path
|
firewalld.exe = path
|
||||||
@@ -85,7 +86,7 @@ func (this *Firewalld) AllowPort(port int, protocol string) error {
|
|||||||
if !this.isReady {
|
if !this.isReady {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, "--add-port="+types.String(port)+"/"+protocol)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+types.String(port)+"/"+protocol)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -95,12 +96,12 @@ func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol
|
|||||||
var port = this.PortRangeString(portRange, protocol)
|
var port = this.PortRangeString(portRange, protocol)
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--add-port="+port, "--permanent")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port, "--permanent")
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--add-port="+port)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,7 @@ func (this *Firewalld) RemovePort(port int, protocol string) error {
|
|||||||
if !this.isReady {
|
if !this.isReady {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, "--remove-port="+types.String(port)+"/"+protocol)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+types.String(port)+"/"+protocol)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,12 +122,12 @@ func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol str
|
|||||||
var port = this.PortRangeString(portRange, protocol)
|
var port = this.PortRangeString(portRange, protocol)
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--remove-port="+port, "--permanent")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port, "--permanent")
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--remove-port="+port)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +160,7 @@ func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
|
|||||||
if timeoutSeconds > 0 {
|
if timeoutSeconds > 0 {
|
||||||
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, args...)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
|
||||||
this.pushCmd(cmd, ip)
|
this.pushCmd(cmd, ip)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -182,7 +183,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
|
|||||||
if timeoutSeconds > 0 {
|
if timeoutSeconds > 0 {
|
||||||
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, args...)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
|
||||||
if async {
|
if async {
|
||||||
this.pushCmd(cmd, ip)
|
this.pushCmd(cmd, ip)
|
||||||
return nil
|
return nil
|
||||||
@@ -209,13 +210,13 @@ func (this *Firewalld) RemoveSourceIP(ip string) error {
|
|||||||
}
|
}
|
||||||
for _, action := range []string{"reject", "drop"} {
|
for _, action := range []string{"reject", "drop"} {
|
||||||
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
|
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
|
||||||
var cmd = exec.Command(this.exe, args...)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Firewalld) pushCmd(cmd *exec.Cmd, denyIP string) {
|
func (this *Firewalld) pushCmd(cmd *executils.Cmd, denyIP string) {
|
||||||
select {
|
select {
|
||||||
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
|
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package firewalls
|
package firewalls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||||
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/firewalls/nftables"
|
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
|
"github.com/google/nftables/expr"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
// check nft status, if being enabled we load it automatically
|
// check nft status, if being enabled we load it automatically
|
||||||
func init() {
|
func init() {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +37,8 @@ func init() {
|
|||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := exec.LookPath("nft")
|
var nftExe = nftables.NftExePath()
|
||||||
if err == nil {
|
if len(nftExe) > 0 {
|
||||||
nftablesFirewall, err := NewNFTablesFirewall()
|
nftablesFirewall, err := NewNFTablesFirewall()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -88,11 +88,15 @@ type blockIPItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewNFTablesFirewall() (*NFTablesFirewall, error) {
|
func NewNFTablesFirewall() (*NFTablesFirewall, error) {
|
||||||
|
conn, err := nftables.NewConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var firewall = &NFTablesFirewall{
|
var firewall = &NFTablesFirewall{
|
||||||
conn: nftables.NewConn(),
|
conn: conn,
|
||||||
dropIPQueue: make(chan *blockIPItem, 4096),
|
dropIPQueue: make(chan *blockIPItem, 4096),
|
||||||
}
|
}
|
||||||
err := firewall.init()
|
err = firewall.init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -110,8 +114,8 @@ type NFTablesFirewall struct {
|
|||||||
allowIPv4Set *nftables.Set
|
allowIPv4Set *nftables.Set
|
||||||
allowIPv6Set *nftables.Set
|
allowIPv6Set *nftables.Set
|
||||||
|
|
||||||
denyIPv4Set *nftables.Set
|
denyIPv4Sets []*nftables.Set
|
||||||
denyIPv6Set *nftables.Set
|
denyIPv6Sets []*nftables.Set
|
||||||
|
|
||||||
firewalld *Firewalld
|
firewalld *Firewalld
|
||||||
|
|
||||||
@@ -120,9 +124,9 @@ type NFTablesFirewall struct {
|
|||||||
|
|
||||||
func (this *NFTablesFirewall) init() error {
|
func (this *NFTablesFirewall) init() error {
|
||||||
// check nft
|
// check nft
|
||||||
nftPath, err := exec.LookPath("nft")
|
var nftPath = nftables.NftExePath()
|
||||||
if err != nil {
|
if len(nftPath) == 0 {
|
||||||
return errors.New("nft not found")
|
return errors.New("'nft' not found")
|
||||||
}
|
}
|
||||||
this.version = this.readVersion(nftPath)
|
this.version = this.readVersion(nftPath)
|
||||||
|
|
||||||
@@ -186,7 +190,7 @@ func (this *NFTablesFirewall) init() error {
|
|||||||
|
|
||||||
// allow set
|
// allow set
|
||||||
// "allow" should be always first
|
// "allow" should be always first
|
||||||
for _, setAction := range []string{"allow", "deny"} {
|
for _, setAction := range []string{"allow", "deny", "deny1", "deny2", "deny3", "deny4"} {
|
||||||
var setName = setAction + "_set"
|
var setName = setAction + "_set"
|
||||||
|
|
||||||
set, err := table.GetSet(setName)
|
set, err := table.GetSet(setName)
|
||||||
@@ -216,32 +220,42 @@ func (this *NFTablesFirewall) init() error {
|
|||||||
if setAction == "allow" {
|
if setAction == "allow" {
|
||||||
this.allowIPv4Set = set
|
this.allowIPv4Set = set
|
||||||
} else {
|
} else {
|
||||||
this.denyIPv4Set = set
|
this.denyIPv4Sets = append(this.denyIPv4Sets, set)
|
||||||
}
|
}
|
||||||
} else if tableDef.IsIPv6 {
|
} else if tableDef.IsIPv6 {
|
||||||
if setAction == "allow" {
|
if setAction == "allow" {
|
||||||
this.allowIPv6Set = set
|
this.allowIPv6Set = set
|
||||||
} else {
|
} else {
|
||||||
this.denyIPv6Set = set
|
this.denyIPv6Sets = append(this.denyIPv6Sets, set)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rule
|
// rule
|
||||||
var ruleName = []byte(setAction)
|
var ruleName = []byte(setAction)
|
||||||
rule, err := chain.GetRuleWithUserData(ruleName)
|
rule, err := chain.GetRuleWithUserData(ruleName)
|
||||||
|
|
||||||
|
// 将以前的drop规则删掉,替换成后面的reject
|
||||||
|
if err == nil && setAction != "allow" && rule != nil && rule.VerDict() == expr.VerdictDrop {
|
||||||
|
deleteErr := chain.DeleteRule(rule)
|
||||||
|
if deleteErr == nil {
|
||||||
|
err = nftables.ErrRuleNotFound
|
||||||
|
rule = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if nftables.IsNotFound(err) {
|
if nftables.IsNotFound(err) {
|
||||||
if tableDef.IsIPv4 {
|
if tableDef.IsIPv4 {
|
||||||
if setAction == "allow" {
|
if setAction == "allow" {
|
||||||
rule, err = chain.AddAcceptIPv4SetRule(setName, ruleName)
|
rule, err = chain.AddAcceptIPv4SetRule(setName, ruleName)
|
||||||
} else {
|
} else {
|
||||||
rule, err = chain.AddDropIPv4SetRule(setName, ruleName)
|
rule, err = chain.AddRejectIPv4SetRule(setName, ruleName)
|
||||||
}
|
}
|
||||||
} else if tableDef.IsIPv6 {
|
} else if tableDef.IsIPv6 {
|
||||||
if setAction == "allow" {
|
if setAction == "allow" {
|
||||||
rule, err = chain.AddAcceptIPv6SetRule(setName, ruleName)
|
rule, err = chain.AddAcceptIPv6SetRule(setName, ruleName)
|
||||||
} else {
|
} else {
|
||||||
rule, err = chain.AddDropIPv6SetRule(setName, ruleName)
|
rule, err = chain.AddRejectIPv6SetRule(setName, ruleName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -265,7 +279,7 @@ func (this *NFTablesFirewall) init() error {
|
|||||||
for ipItem := range this.dropIPQueue {
|
for ipItem := range this.dropIPQueue {
|
||||||
switch ipItem.action {
|
switch ipItem.action {
|
||||||
case "drop":
|
case "drop":
|
||||||
err = this.DropSourceIP(ipItem.ip, ipItem.timeoutSeconds, false)
|
err := this.DropSourceIP(ipItem.ip, ipItem.timeoutSeconds, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Warn("NFTABLES", "drop ip '"+ipItem.ip+"' failed: "+err.Error())
|
remotelogs.Warn("NFTABLES", "drop ip '"+ipItem.ip+"' failed: "+err.Error())
|
||||||
}
|
}
|
||||||
@@ -324,14 +338,14 @@ func (this *NFTablesFirewall) AllowSourceIP(ip string) error {
|
|||||||
if this.allowIPv6Set == nil {
|
if this.allowIPv6Set == nil {
|
||||||
return errors.New("ipv6 ip set is nil")
|
return errors.New("ipv6 ip set is nil")
|
||||||
}
|
}
|
||||||
return this.allowIPv6Set.AddElement(data.To16(), nil)
|
return this.allowIPv6Set.AddElement(data.To16(), nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv4
|
// ipv4
|
||||||
if this.allowIPv4Set == nil {
|
if this.allowIPv4Set == nil {
|
||||||
return errors.New("ipv4 ip set is nil")
|
return errors.New("ipv4 ip set is nil")
|
||||||
}
|
}
|
||||||
return this.allowIPv4Set.AddElement(data.To4(), nil)
|
return this.allowIPv4Set.AddElement(data.To4(), nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RejectSourceIP 拒绝某个源IP连接
|
// RejectSourceIP 拒绝某个源IP连接
|
||||||
@@ -371,22 +385,23 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
|
|||||||
// 再次尝试关闭连接
|
// 再次尝试关闭连接
|
||||||
defer conns.SharedMap.CloseIPConns(ip)
|
defer conns.SharedMap.CloseIPConns(ip)
|
||||||
|
|
||||||
|
var ipLong = configutils.IPString2Long(ip)
|
||||||
if strings.Contains(ip, ":") { // ipv6
|
if strings.Contains(ip, ":") { // ipv6
|
||||||
if this.denyIPv6Set == nil {
|
if len(this.denyIPv6Sets) == 0 {
|
||||||
return errors.New("ipv6 ip set is nil")
|
return errors.New("ipv6 ip set not found")
|
||||||
}
|
}
|
||||||
return this.denyIPv6Set.AddElement(data.To16(), &nftables.ElementOptions{
|
return this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].AddElement(data.To16(), &nftables.ElementOptions{
|
||||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||||
})
|
}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv4
|
// ipv4
|
||||||
if this.denyIPv4Set == nil {
|
if len(this.denyIPv4Sets) == 0 {
|
||||||
return errors.New("ipv4 ip set is nil")
|
return errors.New("ipv4 ip set not found")
|
||||||
}
|
}
|
||||||
return this.denyIPv4Set.AddElement(data.To4(), &nftables.ElementOptions{
|
return this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].AddElement(data.To4(), &nftables.ElementOptions{
|
||||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||||
})
|
}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveSourceIP 删除某个源IP
|
// RemoveSourceIP 删除某个源IP
|
||||||
@@ -396,9 +411,10 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
|||||||
return errors.New("invalid ip '" + ip + "'")
|
return errors.New("invalid ip '" + ip + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ipLong = configutils.IPString2Long(ip)
|
||||||
if strings.Contains(ip, ":") { // ipv6
|
if strings.Contains(ip, ":") { // ipv6
|
||||||
if this.denyIPv6Set != nil {
|
if len(this.denyIPv6Sets) > 0 {
|
||||||
err := this.denyIPv6Set.DeleteElement(data.To16())
|
err := this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].DeleteElement(data.To16())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -415,13 +431,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ipv4
|
// ipv4
|
||||||
if this.allowIPv4Set != nil {
|
if len(this.denyIPv4Sets) > 0 {
|
||||||
err := this.denyIPv4Set.DeleteElement(data.To4())
|
err := this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].DeleteElement(data.To4())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
err = this.allowIPv4Set.DeleteElement(data.To4())
|
if this.allowIPv4Set != nil {
|
||||||
|
err := this.allowIPv4Set.DeleteElement(data.To4())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -432,15 +449,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
|||||||
|
|
||||||
// 读取版本号
|
// 读取版本号
|
||||||
func (this *NFTablesFirewall) readVersion(nftPath string) string {
|
func (this *NFTablesFirewall) readVersion(nftPath string) string {
|
||||||
var cmd = exec.Command(nftPath, "--version")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftPath, "--version")
|
||||||
var output = &bytes.Buffer{}
|
cmd.WithStdout()
|
||||||
cmd.Stdout = output
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputString = output.String()
|
var outputString = cmd.Stdout()
|
||||||
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
|
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
|
||||||
if len(versionMatches) <= 1 {
|
if len(versionMatches) <= 1 {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables_test
|
package nftables_test
|
||||||
|
|
||||||
@@ -11,7 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getIPv4Chain(t *testing.T) *nftables.Chain {
|
func getIPv4Chain(t *testing.T) *nftables.Chain {
|
||||||
var conn = nftables.NewConn()
|
conn, err := nftables.NewConn()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
|
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == nftables.ErrTableNotFound {
|
if err == nftables.ErrTableNotFound {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
@@ -16,10 +15,14 @@ type Conn struct {
|
|||||||
rawConn *nft.Conn
|
rawConn *nft.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn() *Conn {
|
func NewConn() (*Conn, error) {
|
||||||
return &Conn{
|
conn, err := nft.New()
|
||||||
rawConn: &nft.Conn{},
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return &Conn{
|
||||||
|
rawConn: conn,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Conn) Raw() *nft.Conn {
|
func (this *Conn) Raw() *nft.Conn {
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ package nftables_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConn_Test(t *testing.T) {
|
func TestConn_Test(t *testing.T) {
|
||||||
_, err := exec.LookPath("nft")
|
_, err := executils.LookPath("nft")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
var ErrTableNotFound = errors.New("table not found")
|
var ErrTableNotFound = errors.New("table not found")
|
||||||
var ErrChainNotFound = errors.New("chain not found")
|
var ErrChainNotFound = errors.New("chain not found")
|
||||||
@@ -15,5 +18,5 @@ func IsNotFound(err error) bool {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound
|
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound || strings.Contains(err.Error(), "no such file or directory")
|
||||||
}
|
}
|
||||||
|
|||||||
65
internal/firewalls/nftables/expration.go
Normal file
65
internal/firewalls/nftables/expration.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package nftables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Expiration struct {
|
||||||
|
m map[string]time.Time // key => expires time
|
||||||
|
|
||||||
|
lastGCAt int64
|
||||||
|
|
||||||
|
locker sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExpiration() *Expiration {
|
||||||
|
return &Expiration{
|
||||||
|
m: map[string]time.Time{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Expiration) AddUnsafe(key []byte, expires time.Time) {
|
||||||
|
this.m[string(key)] = expires
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Expiration) Add(key []byte, expires time.Time) {
|
||||||
|
this.locker.Lock()
|
||||||
|
this.m[string(key)] = expires
|
||||||
|
this.gc()
|
||||||
|
this.locker.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Expiration) Remove(key []byte) {
|
||||||
|
this.locker.Lock()
|
||||||
|
delete(this.m, string(key))
|
||||||
|
this.locker.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Expiration) Contains(key []byte) bool {
|
||||||
|
this.locker.RLock()
|
||||||
|
expires, ok := this.m[string(key)]
|
||||||
|
if ok && expires.Year() > 2000 && time.Now().After(expires) {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
this.locker.RUnlock()
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Expiration) gc() {
|
||||||
|
// we won't gc too frequently
|
||||||
|
var currentTime = time.Now().Unix()
|
||||||
|
if this.lastGCAt >= currentTime {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.lastGCAt = currentTime
|
||||||
|
|
||||||
|
var now = time.Now().Add(-10 * time.Second) // gc elements expired before 10 seconds ago
|
||||||
|
for key, expires := range this.m {
|
||||||
|
if expires.Year() > 2000 && now.After(expires) {
|
||||||
|
delete(this.m, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
internal/firewalls/nftables/expration_test.go
Normal file
59
internal/firewalls/nftables/expration_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package nftables_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpiration_Add(t *testing.T) {
|
||||||
|
var expiration = nftables.NewExpiration()
|
||||||
|
{
|
||||||
|
expiration.Add([]byte{'a', 'b', 'c'}, time.Now())
|
||||||
|
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
|
||||||
|
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expiration.Add([]byte{'a', 'b', 'c'}, time.Time{})
|
||||||
|
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-1*time.Second))
|
||||||
|
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-10*time.Second))
|
||||||
|
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
|
||||||
|
expiration.Remove([]byte{'a', 'b', 'c'})
|
||||||
|
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
expiration.Add(net.ParseIP("10.254.0.75").To4(), time.Now())
|
||||||
|
t.Log(expiration.Contains(net.ParseIP("10.254.0.75").To4()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNewExpiration(b *testing.B) {
|
||||||
|
var expiration = nftables.NewExpiration()
|
||||||
|
for i := 0; i < 10_000; i++ {
|
||||||
|
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
130
internal/firewalls/nftables/installer.go
Normal file
130
internal/firewalls/nftables/installer.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package nftables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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/remotelogs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !teaconst.IsMain {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
events.On(events.EventReload, func() {
|
||||||
|
// linux only
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeConfig == nil || !nodeConfig.AutoInstallNftables {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getgid() == 0 { // root user only
|
||||||
|
if len(NftExePath()) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goman.New(func() {
|
||||||
|
err := NewInstaller().Install()
|
||||||
|
if err != nil {
|
||||||
|
// 不需要传到API节点
|
||||||
|
logs.Println("[NFTABLES]install nftables failed: " + err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NftExePath 查找nftables可执行文件路径
|
||||||
|
func NftExePath() string {
|
||||||
|
path, _ := executils.LookPath("nft")
|
||||||
|
if len(path) > 0 {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, possiblePath := range []string{
|
||||||
|
"/usr/sbin/nft",
|
||||||
|
} {
|
||||||
|
_, err := os.Stat(possiblePath)
|
||||||
|
if err == nil {
|
||||||
|
return possiblePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Installer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInstaller() *Installer {
|
||||||
|
return &Installer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Installer) Install() error {
|
||||||
|
// linux only
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已经存在
|
||||||
|
if len(NftExePath()) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd *executils.Cmd
|
||||||
|
|
||||||
|
// check dnf
|
||||||
|
dnfExe, err := executils.LookPath("dnf")
|
||||||
|
if err == nil {
|
||||||
|
cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check apt
|
||||||
|
if cmd == nil {
|
||||||
|
aptExe, err := executils.LookPath("apt")
|
||||||
|
if err == nil {
|
||||||
|
cmd = executils.NewCmd(aptExe, "install", "nftables")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check yum
|
||||||
|
if cmd == nil {
|
||||||
|
yumExe, err := executils.LookPath("yum")
|
||||||
|
if err == nil {
|
||||||
|
cmd = executils.NewCmd(yumExe, "-y", "install", "nftables")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.WithTimeout(10 * time.Minute)
|
||||||
|
cmd.WithStderr()
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(err.Error() + ": " + cmd.Stderr())
|
||||||
|
}
|
||||||
|
|
||||||
|
remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
@@ -35,17 +34,25 @@ type Set struct {
|
|||||||
conn *Conn
|
conn *Conn
|
||||||
rawSet *nft.Set
|
rawSet *nft.Set
|
||||||
batch *SetBatch
|
batch *SetBatch
|
||||||
|
|
||||||
|
expiration *Expiration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSet(conn *Conn, rawSet *nft.Set) *Set {
|
func NewSet(conn *Conn, rawSet *nft.Set) *Set {
|
||||||
return &Set{
|
var set = &Set{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
rawSet: rawSet,
|
rawSet: rawSet,
|
||||||
|
expiration: nil,
|
||||||
batch: &SetBatch{
|
batch: &SetBatch{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
rawSet: rawSet,
|
rawSet: rawSet,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// retrieve set elements to improve "delete" speed
|
||||||
|
set.initElements()
|
||||||
|
|
||||||
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Set) Raw() *nft.Set {
|
func (this *Set) Raw() *nft.Set {
|
||||||
@@ -56,12 +63,22 @@ func (this *Set) Name() string {
|
|||||||
return this.rawSet.Name
|
return this.rawSet.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool) error {
|
||||||
|
// check if already exists
|
||||||
|
if this.expiration != nil && !overwrite && this.expiration.Contains(key) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresTime = time.Time{}
|
||||||
var rawElement = nft.SetElement{
|
var rawElement = nft.SetElement{
|
||||||
Key: key,
|
Key: key,
|
||||||
}
|
}
|
||||||
if options != nil {
|
if options != nil {
|
||||||
rawElement.Timeout = options.Timeout
|
rawElement.Timeout = options.Timeout
|
||||||
|
|
||||||
|
if options.Timeout > 0 {
|
||||||
|
expiresTime = time.UnixMilli(time.Now().UnixMilli() + options.Timeout.Milliseconds())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err := this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{
|
err := this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{
|
||||||
rawElement,
|
rawElement,
|
||||||
@@ -71,9 +88,19 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = this.conn.Commit()
|
err = this.conn.Commit()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
|
if this.expiration != nil {
|
||||||
|
this.expiration.Add(key, expiresTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var isFileExistsErr = strings.Contains(err.Error(), "file exists")
|
||||||
|
if !overwrite && isFileExistsErr {
|
||||||
|
// ignore file exists error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// retry if exists
|
// retry if exists
|
||||||
if strings.Contains(err.Error(), "file exists") {
|
if overwrite && isFileExistsErr {
|
||||||
deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
||||||
{
|
{
|
||||||
Key: key,
|
Key: key,
|
||||||
@@ -85,6 +112,11 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
|||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = this.conn.Commit()
|
err = this.conn.Commit()
|
||||||
|
if err == nil {
|
||||||
|
if this.expiration != nil {
|
||||||
|
this.expiration.Add(key, expiresTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,20 +125,25 @@ func (this *Set) AddElement(key []byte, options *ElementOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Set) AddIPElement(ip string, options *ElementOptions) error {
|
func (this *Set) AddIPElement(ip string, options *ElementOptions, overwrite bool) error {
|
||||||
var ipObj = net.ParseIP(ip)
|
var ipObj = net.ParseIP(ip)
|
||||||
if ipObj == nil {
|
if ipObj == nil {
|
||||||
return errors.New("invalid ip '" + ip + "'")
|
return errors.New("invalid ip '" + ip + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.IsIPv4(ip) {
|
if utils.IsIPv4(ip) {
|
||||||
return this.AddElement(ipObj.To4(), options)
|
return this.AddElement(ipObj.To4(), options, overwrite)
|
||||||
} else {
|
} else {
|
||||||
return this.AddElement(ipObj.To16(), options)
|
return this.AddElement(ipObj.To16(), options, overwrite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Set) DeleteElement(key []byte) error {
|
func (this *Set) DeleteElement(key []byte) error {
|
||||||
|
// if set element does not exist, we return immediately
|
||||||
|
if this.expiration != nil && !this.expiration.Contains(key) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
err := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
||||||
{
|
{
|
||||||
Key: key,
|
Key: key,
|
||||||
@@ -116,9 +153,17 @@ func (this *Set) DeleteElement(key []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = this.conn.Commit()
|
err = this.conn.Commit()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
|
if this.expiration != nil {
|
||||||
|
this.expiration.Remove(key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if strings.Contains(err.Error(), "no such file or directory") {
|
if strings.Contains(err.Error(), "no such file or directory") {
|
||||||
err = nil
|
err = nil
|
||||||
|
|
||||||
|
if this.expiration != nil {
|
||||||
|
this.expiration.Remove(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
8
internal/firewalls/nftables/set_ext.go
Normal file
8
internal/firewalls/nftables/set_ext.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
//go:build linux && !plus
|
||||||
|
|
||||||
|
package nftables
|
||||||
|
|
||||||
|
func (this *Set) initElements() {
|
||||||
|
// NOT IMPLEMENTED
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables_test
|
package nftables_test
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ func getIPv4Set(t *testing.T) *nftables.Set {
|
|||||||
|
|
||||||
func TestSet_AddElement(t *testing.T) {
|
func TestSet_AddElement(t *testing.T) {
|
||||||
var set = getIPv4Set(t)
|
var set = getIPv4Set(t)
|
||||||
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second})
|
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables_test
|
package nftables_test
|
||||||
|
|
||||||
@@ -10,7 +9,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getIPv4Table(t *testing.T) *nftables.Table {
|
func getIPv4Table(t *testing.T) *nftables.Table {
|
||||||
var conn = nftables.NewConn()
|
conn, err := nftables.NewConn()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
|
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == nftables.ErrTableNotFound {
|
if err == nftables.ErrTableNotFound {
|
||||||
|
|||||||
30
internal/firewalls/utils.go
Normal file
30
internal/firewalls/utils.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package firewalls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DropTemporaryTo 使用本地防火墙临时拦截IP数据包
|
||||||
|
func DropTemporaryTo(ip string, expiresAt int64) {
|
||||||
|
// 如果为0,则表示是长期有效
|
||||||
|
if expiresAt <= 0 {
|
||||||
|
expiresAt = time.Now().Unix() + 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout = expiresAt - time.Now().Unix()
|
||||||
|
if timeout < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if timeout > 3600 {
|
||||||
|
timeout = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用本地防火墙延长封禁
|
||||||
|
var fw = Firewall()
|
||||||
|
if fw != nil && !fw.IsMock() {
|
||||||
|
// 这里 int(int64) 转换的前提是限制了 timeout <= 3600,否则将有整型溢出的风险
|
||||||
|
_ = fw.DropSourceIP(ip, int(timeout), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ var instanceId = uint64(0)
|
|||||||
|
|
||||||
// New 新创建goroutine
|
// New 新创建goroutine
|
||||||
func New(f func()) {
|
func New(f func()) {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ func New(f func()) {
|
|||||||
|
|
||||||
// NewWithArgs 创建带有参数的goroutine
|
// NewWithArgs 创建带有参数的goroutine
|
||||||
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
||||||
if teaconst.IsDaemon {
|
if !teaconst.IsMain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"os/exec"
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FirewalldAction Firewalld动作管理
|
// FirewalldAction Firewalld动作管理
|
||||||
// 常用命令:
|
// 常用命令:
|
||||||
// - 查询列表: firewall-cmd --list-all
|
// - 查询列表: firewall-cmd --list-all
|
||||||
// - 添加IP:firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
// - 添加IP:firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||||
// - 删除IP:firewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
// - 删除IP:firewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||||
type FirewalldAction struct {
|
type FirewalldAction struct {
|
||||||
BaseAction
|
BaseAction
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
|||||||
path := this.config.Path
|
path := this.config.Path
|
||||||
var err error
|
var err error
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
path, err = exec.LookPath("firewall-cmd")
|
path, err = executils.LookPath("firewall-cmd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if this.firewalldNotFound {
|
if this.firewalldNotFound {
|
||||||
return nil
|
return nil
|
||||||
@@ -144,12 +143,11 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
|||||||
// MAC OS直接返回
|
// MAC OS直接返回
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmd := exec.Command(path, args...)
|
cmd := executils.NewTimeoutCmd(30*time.Second, path, args...)
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(err.Error() + ", output: " + string(stderr.Bytes()))
|
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -16,12 +15,12 @@ import (
|
|||||||
// IPSetAction IPSet动作
|
// IPSetAction IPSet动作
|
||||||
// 相关命令:
|
// 相关命令:
|
||||||
// - 利用Firewalld管理set:
|
// - 利用Firewalld管理set:
|
||||||
// - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
|
// - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
|
||||||
// - 删除:firewall-cmd --permanent --delete-ipset=edge_ip_list
|
// - 删除:firewall-cmd --permanent --delete-ipset=edge_ip_list
|
||||||
// - 重载:firewall-cmd --reload
|
// - 重载:firewall-cmd --reload
|
||||||
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
|
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
|
||||||
// - 利用IPTables管理set:
|
// - 利用IPTables管理set:
|
||||||
// - 添加:iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
|
// - 添加:iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
|
||||||
// - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30
|
// - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30
|
||||||
// - 删除Item: ipset del edge_ip_list 192.168.2.32
|
// - 删除Item: ipset del edge_ip_list 192.168.2.32
|
||||||
// - 创建set:ipset create edge_ip_list hash:ip timeout 0
|
// - 创建set:ipset create edge_ip_list hash:ip timeout 0
|
||||||
@@ -30,16 +29,13 @@ import (
|
|||||||
type IPSetAction struct {
|
type IPSetAction struct {
|
||||||
BaseAction
|
BaseAction
|
||||||
|
|
||||||
config *firewallconfigs.FirewallActionIPSetConfig
|
config *firewallconfigs.FirewallActionIPSetConfig
|
||||||
errorBuf *bytes.Buffer
|
|
||||||
|
|
||||||
ipsetNotfound bool
|
ipsetNotfound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIPSetAction() *IPSetAction {
|
func NewIPSetAction() *IPSetAction {
|
||||||
return &IPSetAction{
|
return &IPSetAction{}
|
||||||
errorBuf: &bytes.Buffer{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
@@ -58,7 +54,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
|
|
||||||
// 创建ipset
|
// 创建ipset
|
||||||
{
|
{
|
||||||
path, err := exec.LookPath("ipset")
|
path, err := executils.LookPath("ipset")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -68,14 +64,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if !bytes.Contains(output, []byte("already exists")) {
|
if !strings.Contains(output, "already exists") {
|
||||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -87,14 +82,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if !bytes.Contains(output, []byte("already exists")) {
|
if !strings.Contains(output, "already exists") {
|
||||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -104,7 +98,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
|
|
||||||
// firewalld
|
// firewalld
|
||||||
if this.config.AutoAddToFirewalld {
|
if this.config.AutoAddToFirewalld {
|
||||||
path, err := exec.LookPath("firewall-cmd")
|
path, err := executils.LookPath("firewall-cmd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -114,16 +108,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
if strings.Contains(output, "NAME_CONFLICT") {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,16 +126,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
if strings.Contains(output, "NAME_CONFLICT") {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,13 +144,11 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,32 +157,28 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload
|
// reload
|
||||||
{
|
{
|
||||||
cmd := exec.Command(path, "--reload")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--reload")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// iptables
|
// iptables
|
||||||
if this.config.AutoAddToIPTables {
|
if this.config.AutoAddToIPTables {
|
||||||
path, err := exec.LookPath("iptables")
|
path, err := executils.LookPath("iptables")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -204,19 +190,17 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查规则是否存在
|
// 检查规则是否存在
|
||||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
var exists = err == nil
|
var exists = err == nil
|
||||||
|
|
||||||
// 添加规则
|
// 添加规则
|
||||||
if !exists {
|
if !exists {
|
||||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,18 +212,16 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查规则是否存在
|
// 检查规则是否存在
|
||||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
var exists = err == nil
|
var exists = err == nil
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +310,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
|||||||
var path = this.config.Path
|
var path = this.config.Path
|
||||||
var err error
|
var err error
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
path, err = exec.LookPath("ipset")
|
path, err = executils.LookPath("ipset")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 找不到ipset命令错误只提示一次
|
// 找不到ipset命令错误只提示一次
|
||||||
if this.ipsetNotfound {
|
if this.ipsetNotfound {
|
||||||
@@ -361,12 +343,11 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errorBuf.Reset()
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
|
||||||
var cmd = exec.Command(path, args...)
|
cmd.WithStderr()
|
||||||
cmd.Stderr = this.errorBuf
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errString = this.errorBuf.String()
|
var errString = cmd.Stderr()
|
||||||
if action == "deleteItem" && strings.Contains(errString, "not added") {
|
if action == "deleteItem" && strings.Contains(errString, "not added") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"os/exec"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPTablesAction IPTables动作
|
// IPTablesAction IPTables动作
|
||||||
// 相关命令:
|
// 相关命令:
|
||||||
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT
|
//
|
||||||
// iptables -A INPUT -s "192.168.2.32" -j REJECT
|
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT
|
||||||
// iptables -D INPUT ...
|
// iptables -A INPUT -s "192.168.2.32" -j REJECT
|
||||||
// iptables -F INPUT
|
// iptables -D INPUT ...
|
||||||
|
// iptables -F INPUT
|
||||||
type IPTablesAction struct {
|
type IPTablesAction struct {
|
||||||
BaseAction
|
BaseAction
|
||||||
|
|
||||||
@@ -71,13 +74,19 @@ func (this *IPTablesAction) runAction(action string, listType IPListType, item *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
// 暂时不支持ipv6
|
||||||
|
// TODO 将来支持ipv6
|
||||||
|
if utils.IsIPv6(item.IpFrom) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if item.Type == "all" {
|
if item.Type == "all" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
path := this.config.Path
|
var path = this.config.Path
|
||||||
var err error
|
var err error
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
path, err = exec.LookPath("iptables")
|
path, err = executils.LookPath("iptables")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if this.iptablesNotFound {
|
if this.iptablesNotFound {
|
||||||
return nil
|
return nil
|
||||||
@@ -85,6 +94,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
|||||||
this.iptablesNotFound = true
|
this.iptablesNotFound = true
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
this.config.Path = path
|
||||||
}
|
}
|
||||||
iptablesAction := ""
|
iptablesAction := ""
|
||||||
switch action {
|
switch action {
|
||||||
@@ -110,16 +120,15 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(path, args...)
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if bytes.Contains(output, []byte("No chain/target/match")) {
|
if strings.Contains(output, "No chain/target/match") {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New(err.Error() + ", output: " + string(output))
|
return errors.New(err.Error() + ", output: " + output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
|
|||||||
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type+": "+err.Error())
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type+": "+err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Compare(newConfigJSON, oldConfigJSON) != 0 {
|
if !bytes.Equal(newConfigJSON, oldConfigJSON) {
|
||||||
_ = oldInstance.Close()
|
_ = oldInstance.Close()
|
||||||
|
|
||||||
// 重新创建
|
// 重新创建
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"os/exec"
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScriptAction 脚本命令动作
|
// ScriptAction 脚本命令动作
|
||||||
@@ -45,25 +45,24 @@ func (this *ScriptAction) DeleteItem(listType IPListType, item *pb.IPItem) error
|
|||||||
|
|
||||||
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
// TODO 智能支持 .sh 脚本文件
|
// TODO 智能支持 .sh 脚本文件
|
||||||
cmd := exec.Command(this.config.Path)
|
var cmd = executils.NewTimeoutCmd(30*time.Second, this.config.Path)
|
||||||
cmd.Env = []string{
|
cmd.WithEnv([]string{
|
||||||
"ACTION=" + action,
|
"ACTION=" + action,
|
||||||
"TYPE=" + item.Type,
|
"TYPE=" + item.Type,
|
||||||
"IP_FROM=" + item.IpFrom,
|
"IP_FROM=" + item.IpFrom,
|
||||||
"IP_TO=" + item.IpTo,
|
"IP_TO=" + item.IpTo,
|
||||||
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
|
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
|
||||||
"LIST_TYPE=" + listType,
|
"LIST_TYPE=" + listType,
|
||||||
}
|
})
|
||||||
if len(this.config.Cwd) > 0 {
|
if len(this.config.Cwd) > 0 {
|
||||||
cmd.Dir = this.config.Cwd
|
cmd.WithDir(this.config.Cwd)
|
||||||
} else {
|
} else {
|
||||||
cmd.Dir = filepath.Dir(this.config.Path)
|
cmd.WithDir(filepath.Dir(this.config.Path))
|
||||||
}
|
}
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(err.Error() + ", output: " + string(stderr.Bytes()))
|
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import "github.com/TeaOSLab/EdgeNode/internal/utils"
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
|
)
|
||||||
|
|
||||||
type IPItemType = string
|
type IPItemType = string
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ func (this *IPItem) containsIPv4(ip uint64) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -56,7 +58,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
|
|||||||
if this.IPFrom != ip {
|
if this.IPFrom != ip {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -64,7 +66,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
|
|||||||
|
|
||||||
// 检查是否包所有IP
|
// 检查是否包所有IP
|
||||||
func (this *IPItem) containsAll() bool {
|
func (this *IPItem) containsAll() bool {
|
||||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package iplibrary
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -72,6 +73,25 @@ func (this *IPList) Contains(ip uint64) bool {
|
|||||||
return item != nil
|
return item != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainsExpires 判断是否包含某个IP
|
||||||
|
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||||
|
this.locker.RLock()
|
||||||
|
if len(this.allItemsMap) > 0 {
|
||||||
|
this.locker.RUnlock()
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = this.lookupIP(ip)
|
||||||
|
|
||||||
|
this.locker.RUnlock()
|
||||||
|
|
||||||
|
if item == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.ExpiredAt, true
|
||||||
|
}
|
||||||
|
|
||||||
// ContainsIPStrings 是否包含一组IP中的任意一个,并返回匹配的第一个Item
|
// ContainsIPStrings 是否包含一组IP中的任意一个,并返回匹配的第一个Item
|
||||||
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
|
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
|
||||||
if len(ipStrings) == 0 {
|
if len(ipStrings) == 0 {
|
||||||
@@ -110,7 +130,7 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.ExpiredAt > 0 && item.ExpiredAt < utils.UnixTime() {
|
if item.ExpiredAt > 0 && item.ExpiredAt < fasttime.Now().Unix() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +175,7 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
|||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对列表进行排序
|
// 对列表进行排序
|
||||||
func (this *IPList) sortItems() {
|
func (this *IPList) sortItems() {
|
||||||
sort.Slice(this.sortedItems, func(i, j int) bool {
|
sort.Slice(this.sortedItems, func(i, j int) bool {
|
||||||
var item1 = this.sortedItems[i]
|
var item1 = this.sortedItems[i]
|
||||||
|
|||||||
@@ -3,28 +3,31 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"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/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IPListDB struct {
|
type IPListDB struct {
|
||||||
db *sql.DB
|
db *dbs.DB
|
||||||
|
|
||||||
itemTableName string
|
itemTableName string
|
||||||
|
versionTableName string
|
||||||
|
|
||||||
deleteExpiredItemsStmt *sql.Stmt
|
deleteExpiredItemsStmt *dbs.Stmt
|
||||||
deleteItemStmt *sql.Stmt
|
deleteItemStmt *dbs.Stmt
|
||||||
insertItemStmt *sql.Stmt
|
insertItemStmt *dbs.Stmt
|
||||||
selectItemsStmt *sql.Stmt
|
selectItemsStmt *dbs.Stmt
|
||||||
selectMaxVersionStmt *sql.Stmt
|
selectMaxItemVersionStmt *dbs.Stmt
|
||||||
|
|
||||||
|
selectVersionStmt *dbs.Stmt
|
||||||
|
updateVersionStmt *dbs.Stmt
|
||||||
|
|
||||||
cleanTicker *time.Ticker
|
cleanTicker *time.Ticker
|
||||||
|
|
||||||
@@ -35,9 +38,10 @@ type IPListDB struct {
|
|||||||
|
|
||||||
func NewIPListDB() (*IPListDB, error) {
|
func NewIPListDB() (*IPListDB, error) {
|
||||||
var db = &IPListDB{
|
var db = &IPListDB{
|
||||||
itemTableName: "ipItems",
|
itemTableName: "ipItems",
|
||||||
dir: filepath.Clean(Tea.Root + "/data"),
|
versionTableName: "versions",
|
||||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
dir: filepath.Clean(Tea.Root + "/data"),
|
||||||
|
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||||
}
|
}
|
||||||
err := db.init()
|
err := db.init()
|
||||||
return db, err
|
return db, err
|
||||||
@@ -55,10 +59,8 @@ func (this *IPListDB) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var path = this.dir + "/ip_list.db"
|
var path = this.dir + "/ip_list.db"
|
||||||
_ = os.Remove(path + "-shm")
|
|
||||||
_ = os.Remove(path + "-wal")
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -71,6 +73,14 @@ func (this *IPListDB) init() error {
|
|||||||
|
|
||||||
this.db = db
|
this.db = db
|
||||||
|
|
||||||
|
// 恢复数据库
|
||||||
|
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||||
|
if len(recoverEnv) > 0 {
|
||||||
|
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
|
||||||
|
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化数据库
|
// 初始化数据库
|
||||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -103,6 +113,15 @@ ON "` + this.itemTableName + `" (
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
|
||||||
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"version" integer DEFAULT 0
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化SQL语句
|
// 初始化SQL语句
|
||||||
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,12 +143,25 @@ ON "` + this.itemTableName + `" (
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectMaxVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
this.db = db
|
this.db = db
|
||||||
|
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
events.On(events.EventQuit, func() {
|
events.OnClose(func() {
|
||||||
_ = this.Close()
|
_ = this.Close()
|
||||||
this.cleanTicker.Stop()
|
this.cleanTicker.Stop()
|
||||||
})
|
})
|
||||||
@@ -164,8 +196,18 @@ func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是删除,则不再创建新记录
|
||||||
|
if item.IsDeleted {
|
||||||
|
return this.UpdateMaxVersion(item.Version)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.UpdateMaxVersion(item.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
|
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
|
||||||
@@ -199,27 +241,63 @@ func (this *IPListDB) ReadMaxVersion() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var row = this.selectMaxVersionStmt.QueryRow()
|
// from version table
|
||||||
if row == nil {
|
{
|
||||||
return 0
|
var row = this.selectVersionStmt.QueryRow()
|
||||||
|
if row == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var version int64
|
||||||
|
err := row.Scan(&version)
|
||||||
|
if err == nil {
|
||||||
|
return version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var version int64
|
|
||||||
err := row.Scan(&version)
|
// from items table
|
||||||
if err != nil {
|
{
|
||||||
return 0
|
var row = this.selectMaxItemVersionStmt.QueryRow()
|
||||||
|
if row == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var version int64
|
||||||
|
err := row.Scan(&version)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return version
|
||||||
}
|
}
|
||||||
return version
|
}
|
||||||
|
|
||||||
|
// UpdateMaxVersion 修改版本号
|
||||||
|
func (this *IPListDB) UpdateMaxVersion(version int64) error {
|
||||||
|
if this.isClosed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := this.updateVersionStmt.Exec(version)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPListDB) Close() error {
|
func (this *IPListDB) Close() error {
|
||||||
this.isClosed = true
|
this.isClosed = true
|
||||||
|
|
||||||
if this.db != nil {
|
if this.db != nil {
|
||||||
_ = this.deleteExpiredItemsStmt.Close()
|
for _, stmt := range []*dbs.Stmt{
|
||||||
_ = this.deleteItemStmt.Close()
|
this.deleteExpiredItemsStmt,
|
||||||
_ = this.insertItemStmt.Close()
|
this.deleteItemStmt,
|
||||||
_ = this.selectItemsStmt.Close()
|
this.insertItemStmt,
|
||||||
_ = this.selectMaxVersionStmt.Close()
|
this.selectItemsStmt,
|
||||||
|
this.selectMaxItemVersionStmt, // ipItems table
|
||||||
|
|
||||||
|
this.selectVersionStmt, // versions table
|
||||||
|
this.updateVersionStmt,
|
||||||
|
} {
|
||||||
|
if stmt != nil {
|
||||||
|
_ = stmt.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.db.Close()
|
return this.db.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,3 +79,15 @@ func TestIPListDB_ReadMaxVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Log(db.ReadMaxVersion())
|
t.Log(db.ReadMaxVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIPListDB_UpdateMaxVersion(t *testing.T) {
|
||||||
|
db, err := iplibrary.NewIPListDB()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = db.UpdateMaxVersion(1027)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(db.ReadMaxVersion())
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,50 +10,54 @@ import (
|
|||||||
|
|
||||||
// AllowIP 检查IP是否被允许访问
|
// AllowIP 检查IP是否被允许访问
|
||||||
// 如果一个IP不在任何名单中,则允许访问
|
// 如果一个IP不在任何名单中,则允许访问
|
||||||
func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool) {
|
func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expiresAt int64) {
|
||||||
if !Tea.IsTesting() { // 如果在测试环境,我们不加入一些白名单,以便于可以在本地和局域网正常测试
|
if !Tea.IsTesting() { // 如果在测试环境,我们不加入一些白名单,以便于可以在本地和局域网正常测试
|
||||||
// 放行lo
|
// 放行lo
|
||||||
if ip == "127.0.0.1" || ip == "::1" {
|
if ip == "127.0.0.1" || ip == "::1" {
|
||||||
return true, true
|
return true, true, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// check node
|
// check node
|
||||||
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
||||||
if err == nil && nodeConfig.IPIsAutoAllowed(ip) {
|
if err == nil && nodeConfig.IPIsAutoAllowed(ip) {
|
||||||
return true, true
|
return true, true, 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipLong = utils.IP2Long(ip)
|
var ipLong = utils.IP2Long(ip)
|
||||||
if ipLong == 0 {
|
if ipLong == 0 {
|
||||||
return false, false
|
return false, false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// check white lists
|
// check white lists
|
||||||
if GlobalWhiteIPList.Contains(ipLong) {
|
if GlobalWhiteIPList.Contains(ipLong) {
|
||||||
return true, true
|
return true, true, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverId > 0 {
|
if serverId > 0 {
|
||||||
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
||||||
if list != nil && list.Contains(ipLong) {
|
if list != nil && list.Contains(ipLong) {
|
||||||
return true, true
|
return true, true, 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check black lists
|
// check black lists
|
||||||
if GlobalBlackIPList.Contains(ipLong) {
|
expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipLong)
|
||||||
return false, false
|
if ok {
|
||||||
|
return false, false, expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverId > 0 {
|
if serverId > 0 {
|
||||||
var list = SharedServerListManager.FindBlackList(serverId, false)
|
var list = SharedServerListManager.FindBlackList(serverId, false)
|
||||||
if list != nil && list.Contains(ipLong) {
|
if list != nil {
|
||||||
return false, false
|
expiresAt, ok = list.ContainsExpires(ipLong)
|
||||||
|
if ok {
|
||||||
|
return false, false, expiresAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, false
|
return true, false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInWhiteList 检查IP是否在白名单中
|
// IsInWhiteList 检查IP是否在白名单中
|
||||||
@@ -73,7 +77,7 @@ func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, ip := range ipStrings {
|
for _, ip := range ipStrings {
|
||||||
isAllowed, _ := AllowIP(ip, serverId)
|
isAllowed, _, _ := AllowIP(ip, serverId)
|
||||||
if !isAllowed {
|
if !isAllowed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"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"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
@@ -18,14 +20,25 @@ var SharedIPListManager = NewIPListManager()
|
|||||||
var IPListUpdateNotify = make(chan bool, 1)
|
var IPListUpdateNotify = make(chan bool, 1)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
if !teaconst.IsMain {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
events.On(events.EventLoaded, func() {
|
events.On(events.EventLoaded, func() {
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
SharedIPListManager.Start()
|
SharedIPListManager.Start()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
events.On(events.EventQuit, func() {
|
events.OnClose(func() {
|
||||||
SharedIPListManager.Stop()
|
SharedIPListManager.Stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var ticker = time.NewTicker(24 * time.Hour)
|
||||||
|
goman.New(func() {
|
||||||
|
for range ticker.C {
|
||||||
|
SharedIPListManager.DeleteExpiredItems()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPListManager IP名单管理
|
// IPListManager IP名单管理
|
||||||
@@ -34,17 +47,20 @@ type IPListManager struct {
|
|||||||
|
|
||||||
db *IPListDB
|
db *IPListDB
|
||||||
|
|
||||||
version int64
|
lastVersion int64
|
||||||
pageSize int64
|
fetchPageSize int64
|
||||||
|
|
||||||
listMap map[int64]*IPList
|
listMap map[int64]*IPList
|
||||||
locker sync.Mutex
|
locker sync.Mutex
|
||||||
|
|
||||||
|
isFirstTime bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIPListManager() *IPListManager {
|
func NewIPListManager() *IPListManager {
|
||||||
return &IPListManager{
|
return &IPListManager{
|
||||||
pageSize: 1000,
|
fetchPageSize: 5_000,
|
||||||
listMap: map[int64]*IPList{},
|
listMap: map[int64]*IPList{},
|
||||||
|
isFirstTime: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +120,11 @@ func (this *IPListManager) init() {
|
|||||||
_ = db.DeleteExpiredItems()
|
_ = db.DeleteExpiredItems()
|
||||||
|
|
||||||
// 本地数据库中最大版本号
|
// 本地数据库中最大版本号
|
||||||
this.version = db.ReadMaxVersion()
|
this.lastVersion = db.ReadMaxVersion()
|
||||||
|
|
||||||
// 从本地数据库中加载
|
// 从本地数据库中加载
|
||||||
var offset int64 = 0
|
var offset int64 = 0
|
||||||
var size int64 = 1000
|
var size int64 = 2_000
|
||||||
for {
|
for {
|
||||||
items, err := db.ReadItems(offset, size)
|
items, err := db.ReadItems(offset, size)
|
||||||
var l = len(items)
|
var l = len(items)
|
||||||
@@ -129,6 +145,17 @@ func (this *IPListManager) init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPListManager) loop() error {
|
func (this *IPListManager) loop() error {
|
||||||
|
// 是否同步IP名单
|
||||||
|
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||||
|
if nodeConfig != nil && !nodeConfig.EnableIPLists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一次同步则打印信息
|
||||||
|
if this.isFirstTime {
|
||||||
|
remotelogs.Println("IP_LIST_MANAGER", "initializing ip items ...")
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
hasNext, err := this.fetch()
|
hasNext, err := this.fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -140,6 +167,12 @@ func (this *IPListManager) loop() error {
|
|||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 第一次同步则打印信息
|
||||||
|
if this.isFirstTime {
|
||||||
|
this.isFirstTime = false
|
||||||
|
remotelogs.Println("IP_LIST_MANAGER", "finished initializing ip items")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +182,8 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
itemsResp, err := rpcClient.IPItemRPC.ListIPItemsAfterVersion(rpcClient.Context(), &pb.ListIPItemsAfterVersionRequest{
|
itemsResp, err := rpcClient.IPItemRPC.ListIPItemsAfterVersion(rpcClient.Context(), &pb.ListIPItemsAfterVersionRequest{
|
||||||
Version: this.version,
|
Version: this.lastVersion,
|
||||||
Size: this.pageSize,
|
Size: this.fetchPageSize,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rpc.IsConnError(err) {
|
if rpc.IsConnError(err) {
|
||||||
@@ -186,6 +219,13 @@ func (this *IPListManager) FindList(listId int64) *IPList {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *IPListManager) DeleteExpiredItems() {
|
||||||
|
if this.db != nil {
|
||||||
|
_ = this.db.DeleteExpiredItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理IP条目
|
||||||
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||||
var changedLists = map[*IPList]zero.Zero{}
|
var changedLists = map[*IPList]zero.Zero{}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
@@ -255,8 +295,8 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
|||||||
|
|
||||||
if fromRemote {
|
if fromRemote {
|
||||||
var latestVersion = items[len(items)-1].Version
|
var latestVersion = items[len(items)-1].Version
|
||||||
if latestVersion > this.version {
|
if latestVersion > this.lastVersion {
|
||||||
this.version = latestVersion
|
this.lastVersion = latestVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ func TestIPListManager_check(t *testing.T) {
|
|||||||
func TestIPListManager_loop(t *testing.T) {
|
func TestIPListManager_loop(t *testing.T) {
|
||||||
manager := NewIPListManager()
|
manager := NewIPListManager()
|
||||||
manager.Start()
|
manager.Start()
|
||||||
manager.pageSize = 10
|
|
||||||
err := manager.loop()
|
err := manager.loop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package metrics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -13,7 +14,11 @@ import (
|
|||||||
var SharedManager = NewManager()
|
var SharedManager = NewManager()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
events.On(events.EventQuit, func() {
|
if !teaconst.IsMain {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
events.OnClose(func() {
|
||||||
SharedManager.Quit()
|
SharedManager.Quit()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"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"
|
||||||
@@ -17,7 +16,6 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -50,11 +48,11 @@ type Task struct {
|
|||||||
|
|
||||||
cleanVersion int32
|
cleanVersion int32
|
||||||
|
|
||||||
insertStatStmt *sql.Stmt
|
insertStatStmt *dbs.Stmt
|
||||||
deleteByVersionStmt *sql.Stmt
|
deleteByVersionStmt *dbs.Stmt
|
||||||
deleteByExpiresTimeStmt *sql.Stmt
|
deleteByExpiresTimeStmt *dbs.Stmt
|
||||||
selectTopStmt *sql.Stmt
|
selectTopStmt *dbs.Stmt
|
||||||
sumStmt *sql.Stmt
|
sumStmt *dbs.Stmt
|
||||||
|
|
||||||
serverIdMap map[int64]zero.Zero // 所有的服务Ids
|
serverIdMap map[int64]zero.Zero // 所有的服务Ids
|
||||||
timeMap map[string]zero.Zero // time => bool
|
timeMap map[string]zero.Zero // time => bool
|
||||||
@@ -91,15 +89,21 @@ func (this *Task) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
|
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
|
||||||
_ = os.Remove(path + "-shm")
|
|
||||||
_ = os.Remove(path + "-wal")
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
this.db = dbs.NewDB(db)
|
this.db = db
|
||||||
|
|
||||||
|
// 恢复数据库
|
||||||
|
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||||
|
if len(recoverEnv) > 0 {
|
||||||
|
for _, indexName := range []string{"serverId", "hash"} {
|
||||||
|
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if teaconst.EnableDBStat {
|
if teaconst.EnableDBStat {
|
||||||
this.db.EnableStat(true)
|
this.db.EnableStat(true)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user