Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4cca10301 | ||
|
|
8597884ec5 | ||
|
|
9f469f5b77 | ||
|
|
5bc4f5b359 | ||
|
|
494ff5b5bb | ||
|
|
bac0060a74 | ||
|
|
062ca1cfcd | ||
|
|
8a4373e984 | ||
|
|
99670e46a5 | ||
|
|
64642c6680 | ||
|
|
f7a7b50eda | ||
|
|
f5265f1832 | ||
|
|
2d6f7522fc | ||
|
|
cfb15e764d | ||
|
|
406d8de999 | ||
|
|
2ca79953b9 | ||
|
|
d067cd8437 | ||
|
|
e4937685bc | ||
|
|
6ac2343aa7 | ||
|
|
1d80bc640d | ||
|
|
b19c431b89 | ||
|
|
a1e1b5ef98 | ||
|
|
e93275ac5c | ||
|
|
a6059ab070 | ||
|
|
5b0f94f317 | ||
|
|
0b4ac55aa6 | ||
|
|
9080c78b13 | ||
|
|
aa7d67e387 | ||
|
|
405b3615fe | ||
|
|
7534af09ed | ||
|
|
cd3bf0cad4 | ||
|
|
41ebdfb7d2 | ||
|
|
fa7d4963cb | ||
|
|
627d9721b3 | ||
|
|
8c3cd53dc3 | ||
|
|
5995be8489 | ||
|
|
eabaa84252 | ||
|
|
40e137e69e | ||
|
|
6f51fe52f8 | ||
|
|
dd4071e7dc | ||
|
|
7e5b3254eb | ||
|
|
ad1947379d | ||
|
|
2ff2afca36 | ||
|
|
9f36678d75 | ||
|
|
288074c8b3 | ||
|
|
0a290251cd | ||
|
|
edf98f1889 | ||
|
|
02c3d24038 | ||
|
|
2a2f4ff7b1 | ||
|
|
d416a2186c | ||
|
|
94b9c23323 | ||
|
|
70d8507c4b | ||
|
|
2eee314ec8 | ||
|
|
93521963b1 | ||
|
|
f456b6bc6b | ||
|
|
d30e2b2cc8 | ||
|
|
25bf4ab55a | ||
|
|
9536c49a8f | ||
|
|
3f19d88246 | ||
|
|
9931d057a9 | ||
|
|
c61ae6c8ba | ||
|
|
249dad3e97 | ||
|
|
f9fbd23a77 | ||
|
|
22eb143dee | ||
|
|
075c11a3cf | ||
|
|
f4258ed00e | ||
|
|
8ac115f865 | ||
|
|
53f109861c | ||
|
|
f034a1cfb3 | ||
|
|
ae74114fca | ||
|
|
cdfc37ac14 | ||
|
|
362a4d9ef4 | ||
|
|
7653c16586 | ||
|
|
f1a4a7ebc6 | ||
|
|
0f0436c7a8 | ||
|
|
cc881c070c | ||
|
|
10db4e3ccd | ||
|
|
a2a33a65e8 | ||
|
|
5a4162987c | ||
|
|
b46744cb13 | ||
|
|
8b7845fd15 | ||
|
|
a129178abc | ||
|
|
02875374aa | ||
|
|
8859625ce7 | ||
|
|
6dccd0ad46 | ||
|
|
18b76013b9 | ||
|
|
fa04c041df | ||
|
|
c8142741ff | ||
|
|
547874aa43 | ||
|
|
45c6b2ddac | ||
|
|
eb145393ab | ||
|
|
d767ca177a | ||
|
|
c8fc2815b9 | ||
|
|
3b2ba1aad6 | ||
|
|
33bb06fbc3 | ||
|
|
3793d684fa |
74
.golangci.yaml
Normal file
74
.golangci.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# https://golangci-lint.run/usage/configuration/
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- ifshort
|
||||
- exhaustivestruct
|
||||
- golint
|
||||
- nosnakecase
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- interfacer
|
||||
- maligned
|
||||
- deadcode
|
||||
- dogsled
|
||||
- wrapcheck
|
||||
- wastedassign
|
||||
- varnamelen
|
||||
- testpackage
|
||||
- thelper
|
||||
- nilerr
|
||||
- sqlclosecheck
|
||||
- paralleltest
|
||||
- nonamedreturns
|
||||
- nlreturn
|
||||
- nakedret
|
||||
- ireturn
|
||||
- interfacebloat
|
||||
- gosmopolitan
|
||||
- gomnd
|
||||
- goerr113
|
||||
- gochecknoglobals
|
||||
- exhaustruct
|
||||
- errorlint
|
||||
- depguard
|
||||
- exhaustive
|
||||
- containedctx
|
||||
- wsl
|
||||
- cyclop
|
||||
- dupword
|
||||
- errchkjson
|
||||
- contextcheck
|
||||
- tagalign
|
||||
- dupl
|
||||
- forbidigo
|
||||
- funlen
|
||||
- goconst
|
||||
- godox
|
||||
- gosec
|
||||
- lll
|
||||
- nestif
|
||||
- revive
|
||||
- unparam
|
||||
- stylecheck
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- gomoddirectives
|
||||
- godot
|
||||
- gofmt
|
||||
- gocognit
|
||||
- mirror
|
||||
- gocyclo
|
||||
- gochecknoinits
|
||||
- gci
|
||||
- maintidx
|
||||
- prealloc
|
||||
- goimports
|
||||
- errname
|
||||
- musttag
|
||||
- forcetypeassert
|
||||
- whitespace
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
@@ -6,8 +6,10 @@ function build() {
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
MUSL_DIR="/usr/local/opt/musl-cross/bin"
|
||||
GCC_X86_64_DIR="/usr/local/Cellar/x86_64-unknown-linux-gnu/10.3.0/bin"
|
||||
GCC_ARM64_DIR="/usr/local/Cellar/aarch64-unknown-linux-gnu/10.3.0/bin"
|
||||
|
||||
# for macOS users: precompiled gcc can be downloaded from https://github.com/messense/homebrew-macos-cross-toolchains
|
||||
GCC_X86_64_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
GCC_ARM64_DIR="//usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
@@ -49,7 +51,7 @@ function build() {
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
||||
cp "$ROOT"/configs/api_node.template.yaml "$DIST"/configs
|
||||
cp "$ROOT"/configs/cluster.template.yaml "$DIST"/configs
|
||||
cp -R "$ROOT"/www "$DIST"/
|
||||
cp -R "$ROOT"/pages "$DIST"/
|
||||
@@ -57,7 +59,10 @@ function build() {
|
||||
# we support TOA on linux only
|
||||
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
|
||||
then
|
||||
mkdir "$DIST/edge-toa"
|
||||
if [ ! -d "$DIST/edge-toa" ]
|
||||
then
|
||||
mkdir "$DIST/edge-toa"
|
||||
fi
|
||||
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
|
||||
fi
|
||||
|
||||
@@ -121,6 +126,11 @@ function build() {
|
||||
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
|
||||
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build failed!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
find "$DIST" -name ".gitignore" -delete
|
||||
|
||||
2
build/configs/.gitignore
vendored
2
build/configs/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node.json
|
||||
api.yaml
|
||||
api_node.yaml
|
||||
cluster.yaml
|
||||
api_cluster.yaml
|
||||
*.cache
|
||||
@@ -1,2 +1,2 @@
|
||||
* `api.template.yaml` - API相关配置模板
|
||||
* `api_node.template.yaml` - API相关配置模板
|
||||
* `cluster.template.yaml` - 通过集群自动接入节点模板
|
||||
@@ -1,4 +0,0 @@
|
||||
rpc:
|
||||
endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
3
build/configs/api_node.template.yaml
Normal file
3
build/configs/api_node.template.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
rpc.endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/apps"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
@@ -17,17 +20,90 @@ import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
|
||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog|uninstall]").
|
||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk|cache.garbage]").
|
||||
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
|
||||
|
||||
app.On("start:before", func() {
|
||||
// validate config
|
||||
_, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
// validate cluster config
|
||||
_, clusterErr := configs.LoadClusterConfig()
|
||||
if clusterErr != nil { // fail again
|
||||
fmt.Println("[ERROR]start failed: load api config from '" + Tea.ConfigFile(configs.ConfigFileName) + "' failed: " + err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
app.On("uninstall", func() {
|
||||
// service
|
||||
fmt.Println("Uninstall service ...")
|
||||
var manager = utils.NewServiceManager(teaconst.ProcessName, teaconst.ProductName)
|
||||
go func() {
|
||||
_ = manager.Uninstall()
|
||||
}()
|
||||
|
||||
// stop
|
||||
fmt.Println("Stopping ...")
|
||||
_, _ = gosock.NewTmpSock(teaconst.ProcessName).SendTimeout(&gosock.Command{Code: "stop"}, 1*time.Second)
|
||||
|
||||
// delete files
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var dir = filepath.Dir(filepath.Dir(exe)) // ROOT / bin / exe
|
||||
|
||||
// verify dir
|
||||
{
|
||||
fmt.Println("Checking '" + dir + "' ...")
|
||||
for _, subDir := range []string{"bin/" + filepath.Base(exe), "configs", "logs"} {
|
||||
_, err := os.Stat(dir + "/" + subDir)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]program directory structure has been broken, please remove it manually.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removing '" + dir + "' ...")
|
||||
err := os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]remove failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// delete symbolic links
|
||||
fmt.Println("Removing symbolic links ...")
|
||||
_ = os.Remove("/usr/bin/" + teaconst.ProcessName)
|
||||
_ = os.Remove("/var/log/" + teaconst.ProcessName)
|
||||
|
||||
// delete configs
|
||||
// nothing to delete for EdgeNode
|
||||
|
||||
// delete sock
|
||||
fmt.Println("Removing temporary files ...")
|
||||
var tempDir = os.TempDir()
|
||||
_ = os.Remove(tempDir + "/" + teaconst.ProcessName + ".sock")
|
||||
_ = os.Remove(tempDir + "/" + teaconst.AccessLogSockName)
|
||||
|
||||
// cache ...
|
||||
fmt.Println("Please delete cache directories by yourself.")
|
||||
|
||||
// done
|
||||
fmt.Println("[DONE]")
|
||||
})
|
||||
app.On("test", func() {
|
||||
err := nodes.NewNode().Test()
|
||||
if err != nil {
|
||||
@@ -375,7 +451,7 @@ func main() {
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
} else {
|
||||
fmt.Printf("Speed: %.2fMB/s\n", speedMB)
|
||||
fmt.Printf("Speed: %.0fMB/s\n", speedMB)
|
||||
if isFast {
|
||||
fmt.Println("IsFast: true")
|
||||
} else {
|
||||
@@ -389,6 +465,45 @@ func main() {
|
||||
fmt.Println("Usage: edge-node disk [speed]")
|
||||
}
|
||||
})
|
||||
app.On("cache.garbage", func() {
|
||||
fmt.Println("scanning ...")
|
||||
|
||||
var shouldDelete bool
|
||||
for _, arg := range os.Args {
|
||||
if strings.TrimLeft(arg, "-") == "delete" {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
reply, err := sock.Send(&gosock.Command{
|
||||
Code: "cache.garbage",
|
||||
Params: map[string]any{"delete": shouldDelete},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var params = maps.NewMap(reply.Params)
|
||||
if params.GetBool("isOk") {
|
||||
var count = params.GetInt("count")
|
||||
fmt.Println("found", count, "bad caches")
|
||||
|
||||
if count > 0 {
|
||||
fmt.Println("======")
|
||||
var sampleFiles = params.GetSlice("sampleFiles")
|
||||
for _, file := range sampleFiles {
|
||||
fmt.Println(types.String(file))
|
||||
}
|
||||
if count > len(sampleFiles) {
|
||||
fmt.Println("... more files")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[ERROR]" + params.GetString("error"))
|
||||
}
|
||||
})
|
||||
app.Run(func() {
|
||||
var node = nodes.NewNode()
|
||||
node.Start()
|
||||
|
||||
30
go.mod
30
go.mod
@@ -13,32 +13,33 @@ require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/aws/aws-sdk-go v1.44.279
|
||||
github.com/baidubce/bce-sdk-go v0.9.153
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/nftables v0.1.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508
|
||||
github.com/iwind/gowebp v0.0.0-20230911074406-2e4e7fd0b59f
|
||||
github.com/klauspost/compress v1.16.5
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mssola/useragent v1.0.0
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0
|
||||
github.com/quic-go/quic-go v0.36.1
|
||||
github.com/quic-go/quic-go v0.38.1
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/sys v0.11.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -50,9 +51,10 @@ require (
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
@@ -60,23 +62,21 @@ require (
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
||||
|
||||
57
go.sum
57
go.sum
@@ -7,6 +7,8 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
|
||||
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/baidubce/bce-sdk-go v0.9.153 h1:h5l2EXehe4C4/bdlAPBaULrbnEDgIu5HOYgniN7bjGM=
|
||||
github.com/baidubce/bce-sdk-go v0.9.153/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -53,8 +55,10 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
|
||||
@@ -69,6 +73,8 @@ github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508 h1:fjKiHAyPQmdwuw1DQ2BI1JTbhUWAtI3Kr9wIZQBdRgQ=
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
||||
github.com/iwind/gowebp v0.0.0-20230911074406-2e4e7fd0b59f h1:b+YNSK4PgRU4u5YuYW8W4dHO3LNsG7XvX2dJQK0jOf8=
|
||||
github.com/iwind/gowebp v0.0.0-20230911074406-2e4e7fd0b59f/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
||||
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
|
||||
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
@@ -93,8 +99,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
|
||||
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
|
||||
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||
@@ -111,7 +117,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
|
||||
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -126,12 +135,14 @@ github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYX
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
|
||||
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc=
|
||||
github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg=
|
||||
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
|
||||
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
|
||||
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=
|
||||
@@ -170,10 +181,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
|
||||
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=
|
||||
@@ -188,8 +199,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -214,8 +225,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -228,8 +239,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.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=
|
||||
@@ -237,8 +248,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -263,3 +274,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rogchap.com/v8go v0.9.0 h1:wYbUCO4h6fjTamziHrzyrPnpFNuzPpjZY+nfmZjNaew=
|
||||
rogchap.com/v8go v0.9.0/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -87,8 +89,8 @@ func (this *AppCmd) Print() {
|
||||
fmt.Println("")
|
||||
fmt.Println("Options:")
|
||||
|
||||
spaces := 20
|
||||
max := 40
|
||||
var spaces = 20
|
||||
var max = 40
|
||||
for _, option := range this.options {
|
||||
l := len(option.Code)
|
||||
if l < max && l > spaces {
|
||||
@@ -126,9 +128,12 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
var mainArg = args[0]
|
||||
this.callDirective(mainArg + ":before")
|
||||
|
||||
switch mainArg {
|
||||
case "-v", "version", "-version", "--version":
|
||||
this.runVersion()
|
||||
return
|
||||
@@ -151,19 +156,19 @@ func (this *AppCmd) Run(main func()) {
|
||||
|
||||
// 查找指令
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == args[0] {
|
||||
if directive.Arg == mainArg {
|
||||
directive.Callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("unknown command '" + args[0] + "'")
|
||||
fmt.Println("unknown command '" + mainArg + "'")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
@@ -191,7 +196,7 @@ func (this *AppCmd) runStart() {
|
||||
|
||||
_ = os.Setenv("EdgeBackground", "on")
|
||||
|
||||
cmd := exec.Command(os.Args[0])
|
||||
var cmd = exec.Command(this.exe())
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Foreground: false,
|
||||
Setsid: true,
|
||||
@@ -203,6 +208,9 @@ func (this *AppCmd) runStart() {
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
@@ -277,3 +285,69 @@ func (this *AppCmd) ParseOptions(args []string) map[string][]string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *AppCmd) exe() string {
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
exe = os.Args[0]
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
func (this *AppCmd) createSymLinks() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorList = []string{}
|
||||
|
||||
// bin
|
||||
{
|
||||
var target = "/usr/bin/" + teaconst.ProcessName
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != exe {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(exe, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log
|
||||
{
|
||||
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
|
||||
var target = "/var/log/" + teaconst.ProcessName + ".log"
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != realPath {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(realPath, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
return errors.New(strings.Join(errorList, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AppCmd) callDirective(code string) {
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == code {
|
||||
if directive.Callback != nil {
|
||||
directive.Callback()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ var (
|
||||
ErrEntityTooLarge = errors.New("entity too large")
|
||||
ErrWritingUnavailable = errors.New("writing unavailable")
|
||||
ErrWritingQueueFull = errors.New("writing queue full")
|
||||
ErrTooManyOpenFiles = errors.New("too many open files")
|
||||
ErrServerIsBusy = errors.New("server is busy")
|
||||
)
|
||||
|
||||
// CapacityError 容量错误
|
||||
@@ -38,7 +38,7 @@ func CanIgnoreErr(err error) bool {
|
||||
err == ErrEntityTooLarge ||
|
||||
err == ErrWritingUnavailable ||
|
||||
err == ErrWritingQueueFull ||
|
||||
err == ErrTooManyOpenFiles {
|
||||
err == ErrServerIsBusy {
|
||||
return true
|
||||
}
|
||||
_, ok := err.(*CapacityError)
|
||||
|
||||
@@ -3,7 +3,6 @@ package caches
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemType = int
|
||||
@@ -16,7 +15,7 @@ const (
|
||||
// 计算当前周
|
||||
// 不要用YW,因为需要计算两周是否临近
|
||||
func currentWeek() int32 {
|
||||
return int32(time.Now().Unix() / 86400)
|
||||
return int32(fasttime.Now().Unix() / 86400)
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
@@ -29,10 +28,7 @@ type Item struct {
|
||||
MetaSize int64 `json:"metaSize"`
|
||||
Host string `json:"host"` // 主机名
|
||||
ServerId int64 `json:"serverId"` // 服务ID
|
||||
|
||||
Week1Hits int64 `json:"week1Hits"`
|
||||
Week2Hits int64 `json:"week2Hits"`
|
||||
Week int32 `json:"week"`
|
||||
Week int32 `json:"week"`
|
||||
}
|
||||
|
||||
func (this *Item) IsExpired() bool {
|
||||
@@ -47,20 +43,6 @@ func (this *Item) Size() int64 {
|
||||
return this.HeaderSize + this.BodySize
|
||||
}
|
||||
|
||||
func (this *Item) IncreaseHit(week int32) {
|
||||
if this.Week == week {
|
||||
this.Week2Hits++
|
||||
} else {
|
||||
if week-this.Week == 1 {
|
||||
this.Week1Hits = this.Week2Hits
|
||||
} else {
|
||||
this.Week1Hits = 0
|
||||
}
|
||||
this.Week2Hits = 1
|
||||
this.Week = week
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Item) RequestURI() string {
|
||||
var schemeIndex = strings.Index(this.Key, "://")
|
||||
if schemeIndex <= 0 {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -11,27 +12,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestItem_IncreaseHit(t *testing.T) {
|
||||
var week = currentWeek()
|
||||
|
||||
var item = &Item{}
|
||||
//item.Week = 2704
|
||||
item.Week2Hits = 100
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
}
|
||||
|
||||
func TestItems_Memory(t *testing.T) {
|
||||
var stat = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = []*Item{}
|
||||
var items = []*caches.Item{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
items = append(items, &Item{
|
||||
items = append(items, &caches.Item{
|
||||
Key: types.String(i),
|
||||
})
|
||||
}
|
||||
@@ -41,18 +29,11 @@ func TestItems_Memory(t *testing.T) {
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
var weekItems = make(map[string]*Item, 10_000_000)
|
||||
|
||||
for _, item := range items {
|
||||
weekItems[item.Key] = item
|
||||
}
|
||||
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory3 = stat.HeapInuse
|
||||
t.Log(memory2, memory3, (memory3-memory2)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(len(items), len(weekItems))
|
||||
}
|
||||
|
||||
func TestItems_Memory2(t *testing.T) {
|
||||
@@ -88,7 +69,7 @@ func TestItem_RequestURI(t *testing.T) {
|
||||
"https://goedge.cn:8080/hello/world",
|
||||
"https://goedge.cn/hello/world?v=1&t=123",
|
||||
} {
|
||||
var item = &Item{Key: u}
|
||||
var item = &caches.Item{Key: u}
|
||||
t.Log(u, "=>", item.RequestURI())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package caches
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
@@ -134,7 +135,7 @@ func (this *FileList) Exist(hash string) (bool, error) {
|
||||
var expiredAt int64
|
||||
err := row.Scan(&expiredAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
@@ -143,6 +144,18 @@ func (this *FileList) Exist(hash string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (this *FileList) ExistQuick(hash string) (isReady bool, found bool) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() || !db.HashMapIsLoaded() {
|
||||
return
|
||||
}
|
||||
|
||||
isReady = true
|
||||
found = db.hashMap.Exist(hash)
|
||||
return
|
||||
}
|
||||
|
||||
// CleanPrefix 清理某个前缀的缓存数据
|
||||
func (this *FileList) CleanPrefix(prefix string) error {
|
||||
if len(prefix) == 0 {
|
||||
@@ -256,16 +269,10 @@ func (this *FileList) PurgeLFU(count int, callback func(hash string) error) erro
|
||||
|
||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||
for _, hash := range hashStrings {
|
||||
notFound, err := this.remove(hash)
|
||||
_, err = this.remove(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if notFound {
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return db.WrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = callback(hash)
|
||||
if err != nil {
|
||||
@@ -372,6 +379,15 @@ func (this *FileList) GetDB(hash string) *FileListDB {
|
||||
return this.dbList[fnv.HashString(hash)%CountFileDB]
|
||||
}
|
||||
|
||||
func (this *FileList) HashMapIsLoaded() bool {
|
||||
for _, db := range this.dbList {
|
||||
if !db.HashMapIsLoaded() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
@@ -393,11 +409,6 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
// when remove file item, no any extra information needed
|
||||
this.onRemove(nil)
|
||||
@@ -493,9 +504,6 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
MetaSize: metaSize,
|
||||
Host: host,
|
||||
ServerId: serverId,
|
||||
Week1Hits: 0,
|
||||
Week2Hits: 0,
|
||||
Week: 0,
|
||||
})
|
||||
if err != nil {
|
||||
if brokenOnError {
|
||||
|
||||
@@ -4,6 +4,7 @@ package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
@@ -33,10 +34,10 @@ type FileListDB struct {
|
||||
hashMap *FileListHashMap
|
||||
|
||||
itemsTableName string
|
||||
hitsTableName string
|
||||
|
||||
isClosed bool
|
||||
isReady bool
|
||||
isClosed bool // 是否已关闭
|
||||
isReady bool // 是否已完成初始化
|
||||
hashMapIsLoaded bool // Hash是否已加载
|
||||
|
||||
// cacheItems
|
||||
existsByHashStmt *dbs.Stmt // 根据hash检查是否存在
|
||||
@@ -56,11 +57,6 @@ type FileListDB struct {
|
||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||
updateAccessWeekSQL string // 修改访问日期
|
||||
|
||||
// hits
|
||||
insertHitSQL string // 写入数据
|
||||
increaseHitSQL string // 增加点击量
|
||||
deleteHitByHashSQL string // 根据hash删除数据
|
||||
}
|
||||
|
||||
func NewFileListDB() *FileListDB {
|
||||
@@ -83,7 +79,7 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
// 这里不能加 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 {
|
||||
return errors.New("open write database failed: " + err.Error())
|
||||
return fmt.Errorf("open write database failed: %w", err)
|
||||
}
|
||||
|
||||
writeDB.SetMaxOpenConns(1)
|
||||
@@ -125,7 +121,7 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
// read db
|
||||
readDB, err := dbs.OpenReader("file:" + dbPath + "?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize))
|
||||
if err != nil {
|
||||
return errors.New("open read database failed: " + err.Error())
|
||||
return fmt.Errorf("open read database failed: %w", err)
|
||||
}
|
||||
|
||||
readDB.SetMaxOpenConns(runtime.NumCPU())
|
||||
@@ -141,12 +137,11 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
|
||||
func (this *FileListDB) Init() error {
|
||||
this.itemsTableName = "cacheItems"
|
||||
this.hitsTableName = "hits"
|
||||
|
||||
// 创建
|
||||
var err = this.initTables(1)
|
||||
if err != nil {
|
||||
return errors.New("init tables failed: " + err.Error())
|
||||
return fmt.Errorf("init tables failed: %w", err)
|
||||
}
|
||||
|
||||
// 常用语句
|
||||
@@ -199,35 +194,10 @@ func (this *FileListDB) Init() error {
|
||||
|
||||
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
|
||||
|
||||
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
||||
|
||||
this.increaseHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?) ON CONFLICT("hash") DO UPDATE SET "week1Hits"=IIF("week"=?, "week1Hits", "week2Hits"), "week2Hits"=IIF("week"=?, "week2Hits"+1, 1), "week"=?`
|
||||
|
||||
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
||||
|
||||
this.isReady = true
|
||||
|
||||
// 加载HashMap
|
||||
go func() {
|
||||
err := this.hashMap.Load(this)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||
|
||||
// 自动修复错误
|
||||
// TODO 将来希望能尽可能恢复以往数据库中的内容
|
||||
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
|
||||
_ = this.Close()
|
||||
this.deleteDB()
|
||||
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
|
||||
err = this.Open(this.dbPath)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
|
||||
} else {
|
||||
_ = this.Init()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
go this.loadHashMap()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -351,25 +321,18 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
|
||||
|
||||
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
||||
var week = timeutil.Format("YW")
|
||||
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
||||
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteHitAsync(hash string) error {
|
||||
this.writeBatch.Add(this.deleteHitByHashSQL, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) CleanPrefix(prefix string) error {
|
||||
if !this.isReady {
|
||||
return nil
|
||||
}
|
||||
var count = int64(10000)
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
for {
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+DefaultStaleCacheSeconds, unixTime, prefix)
|
||||
if err != nil {
|
||||
return this.WrapError(err)
|
||||
}
|
||||
@@ -413,15 +376,14 @@ func (this *FileListDB) CleanMatchKey(key string) error {
|
||||
queryKey = strings.Replace(queryKey, "*", "%", 1)
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey+SuffixAll+"%")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -455,10 +417,9 @@ func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||
queryPrefix += "%"
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -539,7 +500,11 @@ func (this *FileListDB) WrapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(err.Error() + "(file: " + this.dbPath + ")")
|
||||
return fmt.Errorf("%w (file: %s)", err, this.dbPath)
|
||||
}
|
||||
|
||||
func (this *FileListDB) HashMapIsLoaded() bool {
|
||||
return this.hashMapIsLoaded
|
||||
}
|
||||
|
||||
// 初始化
|
||||
@@ -602,32 +567,9 @@ ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除hits表
|
||||
{
|
||||
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"week1Hits" integer DEFAULT 0,
|
||||
"week2Hits" integer DEFAULT 0,
|
||||
"week" varchar(6)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
|
||||
ON "` + this.hitsTableName + `" (
|
||||
"hash" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
// 尝试删除重建
|
||||
if times < 3 {
|
||||
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
|
||||
if dropErr == nil {
|
||||
return this.initTables(times + 1)
|
||||
}
|
||||
return this.WrapError(err)
|
||||
}
|
||||
|
||||
return this.WrapError(err)
|
||||
}
|
||||
_, _ = this.writeDB.Exec(`DROP TABLE "hits"`)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -661,12 +603,11 @@ func (this *FileListDB) shouldRecover() bool {
|
||||
}
|
||||
var errString = ""
|
||||
var shouldRecover = false
|
||||
for result.Next() {
|
||||
err = result.Scan(&errString)
|
||||
if result.Next() {
|
||||
_ = result.Scan(&errString)
|
||||
if strings.TrimSpace(errString) != "ok" {
|
||||
shouldRecover = true
|
||||
}
|
||||
break
|
||||
}
|
||||
_ = result.Close()
|
||||
return shouldRecover
|
||||
@@ -678,3 +619,30 @@ func (this *FileListDB) deleteDB() {
|
||||
_ = os.Remove(this.dbPath + "-shm")
|
||||
_ = os.Remove(this.dbPath + "-wal")
|
||||
}
|
||||
|
||||
// 加载Hash列表
|
||||
func (this *FileListDB) loadHashMap() {
|
||||
this.hashMapIsLoaded = false
|
||||
|
||||
err := this.hashMap.Load(this)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||
|
||||
// 自动修复错误
|
||||
// TODO 将来希望能尽可能恢复以往数据库中的内容
|
||||
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
|
||||
_ = this.Close()
|
||||
this.deleteDB()
|
||||
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
|
||||
err = this.Open(this.dbPath)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
|
||||
} else {
|
||||
_ = this.Init()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.hashMapIsLoaded = true
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
@@ -34,28 +33,6 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
t.Log("[", len(hashList), "]", hashList)
|
||||
}
|
||||
|
||||
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
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()
|
||||
|
||||
@@ -69,6 +46,9 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
@@ -94,6 +74,9 @@ func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
|
||||
@@ -281,11 +281,6 @@ func TestFileList_PurgeLFU(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = list.IncreaseHit(stringutil.Md5("123456"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var count = 0
|
||||
err = list.PurgeLFU(caches.CountFileDB*2, func(hash string) error {
|
||||
t.Log(hash)
|
||||
@@ -356,41 +351,6 @@ func TestFileList_CleanAll(t *testing.T) {
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestFileList_IncreaseHit(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
var count = 1_000_000
|
||||
|
||||
if !testutils.IsSingleTesting() {
|
||||
count = 10
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestFileList_UpgradeV3(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -19,9 +18,6 @@ type MemoryList struct {
|
||||
|
||||
itemMaps map[string]map[string]*Item // prefix => { hash => item }
|
||||
|
||||
weekItemMaps map[int32]map[string]zero.Zero // week => { hash => Zero }
|
||||
minWeek int32
|
||||
|
||||
prefixes []string
|
||||
locker sync.RWMutex
|
||||
onAdd func(item *Item)
|
||||
@@ -32,9 +28,7 @@ type MemoryList struct {
|
||||
|
||||
func NewMemoryList() ListInterface {
|
||||
return &MemoryList{
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
weekItemMaps: map[int32]map[string]zero.Zero{},
|
||||
minWeek: currentWeek(),
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +50,6 @@ func (this *MemoryList) Reset() error {
|
||||
for key := range this.itemMaps {
|
||||
this.itemMaps[key] = map[string]*Item{}
|
||||
}
|
||||
this.weekItemMaps = map[int32]map[string]zero.Zero{}
|
||||
this.locker.Unlock()
|
||||
|
||||
atomic.StoreInt64(&this.count, 0)
|
||||
@@ -65,10 +58,6 @@ func (this *MemoryList) Reset() error {
|
||||
}
|
||||
|
||||
func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
if item.Week == 0 {
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
prefix := this.prefix(hash)
|
||||
@@ -81,14 +70,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
// 先删除,为了可以正确触发统计
|
||||
oldItem, ok := itemMap[hash]
|
||||
if ok {
|
||||
// 从week map中删除
|
||||
if oldItem.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[oldItem.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// 回调
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(oldItem)
|
||||
@@ -104,14 +85,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
|
||||
itemMap[hash] = item
|
||||
|
||||
// week map
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
wm[hash] = zero.New()
|
||||
} else {
|
||||
this.weekItemMaps[item.Week] = map[string]zero.Zero{hash: zero.New()}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -242,14 +215,6 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -261,12 +226,12 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
// callback 每次发现过期key的调用
|
||||
func (this *MemoryList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
this.locker.Lock()
|
||||
deletedHashList := []string{}
|
||||
var deletedHashList = []string{}
|
||||
|
||||
if this.purgeIndex >= len(this.prefixes) {
|
||||
this.purgeIndex = 0
|
||||
}
|
||||
prefix := this.prefixes[this.purgeIndex]
|
||||
var prefix = this.prefixes[this.purgeIndex]
|
||||
|
||||
this.purgeIndex++
|
||||
|
||||
@@ -290,14 +255,6 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) (int,
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
countFound++
|
||||
}
|
||||
|
||||
@@ -322,63 +279,48 @@ func (this *MemoryList) PurgeLFU(count int, callback func(hash string) error) er
|
||||
return nil
|
||||
}
|
||||
|
||||
var week = currentWeek()
|
||||
if this.minWeek > week {
|
||||
this.minWeek = week
|
||||
}
|
||||
|
||||
var deletedHashList = []string{}
|
||||
|
||||
var week = currentWeek()
|
||||
var round = 0
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
Loop:
|
||||
for w := this.minWeek; w <= week; w++ {
|
||||
this.minWeek = w
|
||||
for {
|
||||
var found = false
|
||||
round++
|
||||
for _, itemMap := range this.itemMaps {
|
||||
for hash, item := range itemMap {
|
||||
found = true
|
||||
|
||||
this.locker.Lock()
|
||||
wm, ok := this.weekItemMaps[w]
|
||||
if ok {
|
||||
var wc = len(wm)
|
||||
if wc == 0 {
|
||||
delete(this.weekItemMaps, w)
|
||||
} else {
|
||||
if wc <= count {
|
||||
delete(this.weekItemMaps, w)
|
||||
if week-item.Week <= 1 /** 最近有在使用 **/ && round <= 3 /** 查找轮数过多还不满足数量要求的就不再限制 **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO 未来支持按照点击量排序
|
||||
for hash := range wm {
|
||||
count--
|
||||
|
||||
if count < 0 {
|
||||
this.locker.Unlock()
|
||||
break Loop
|
||||
}
|
||||
|
||||
delete(wm, hash)
|
||||
|
||||
itemMap, ok := this.itemMaps[this.prefix(hash)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item, ok := itemMap[hash]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
count--
|
||||
if count <= 0 {
|
||||
break Loop
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
} else {
|
||||
delete(this.weekItemMaps, w)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
|
||||
// 执行外部操作
|
||||
for _, hash := range deletedHashList {
|
||||
if callback != nil {
|
||||
@@ -451,23 +393,7 @@ func (this *MemoryList) IncreaseHit(hash string) error {
|
||||
|
||||
item, ok := itemMap[hash]
|
||||
if ok {
|
||||
var week = currentWeek()
|
||||
|
||||
// 交换位置
|
||||
if item.Week > 0 && item.Week != week {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
wm, ok = this.weekItemMaps[week]
|
||||
if ok {
|
||||
wm[hash] = zero.New()
|
||||
} else {
|
||||
this.weekItemMaps[week] = map[string]zero.Zero{hash: zero.New()}
|
||||
}
|
||||
}
|
||||
|
||||
item.IncreaseHit(week)
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -32,7 +34,6 @@ func TestMemoryList_Add(t *testing.T) {
|
||||
})
|
||||
t.Log(list.prefixes)
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
@@ -51,7 +52,6 @@ func TestMemoryList_Remove(t *testing.T) {
|
||||
})
|
||||
_ = list.Remove("b")
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ func TestMemoryList_Purge(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
_, _ = list.Purge(100, func(hash string) error {
|
||||
@@ -172,27 +171,64 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestMapRandomDelete(t *testing.T) {
|
||||
var countMap = map[int]int{} // k => count
|
||||
|
||||
for j := 0; j < 1_000_000; j++ {
|
||||
var m = map[int]bool{}
|
||||
for i := 0; i < 100; i++ {
|
||||
m[i] = true
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
count++
|
||||
if count >= 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for k := range m {
|
||||
countMap[k]++
|
||||
}
|
||||
}
|
||||
|
||||
var counts = []int{}
|
||||
for _, count := range countMap {
|
||||
counts = append(counts, count)
|
||||
}
|
||||
sort.Ints(counts)
|
||||
t.Log("["+types.String(len(counts))+"]", counts)
|
||||
}
|
||||
|
||||
func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
list.minWeek = 2704
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
t.Log("current week:", currentWeek())
|
||||
|
||||
_ = list.Add("1", &Item{})
|
||||
_ = list.Add("2", &Item{})
|
||||
_ = list.Add("3", &Item{})
|
||||
_ = list.Add("4", &Item{})
|
||||
_ = list.Add("5", &Item{})
|
||||
_ = list.Add("6", &Item{Week: 2704})
|
||||
_ = list.Add("7", &Item{Week: 2704})
|
||||
_ = list.Add("8", &Item{Week: 2705})
|
||||
|
||||
err := list.PurgeLFU(2, func(hash string) error {
|
||||
//_ = list.IncreaseHit("1")
|
||||
//_ = list.IncreaseHit("2")
|
||||
//_ = list.IncreaseHit("3")
|
||||
//_ = list.IncreaseHit("4")
|
||||
//_ = list.IncreaseHit("5")
|
||||
|
||||
count, err := list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("count items before purge:", count)
|
||||
|
||||
err = list.PurgeLFU(5, func(hash string) error {
|
||||
t.Log("purge lfu:", hash)
|
||||
return nil
|
||||
})
|
||||
@@ -201,40 +237,18 @@ func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_IncreaseHit(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
_ = list.Add("a", item)
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
count, err = list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("count items left:", count)
|
||||
}
|
||||
|
||||
func TestMemoryList_CleanAll(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
_ = list.CleanAll()
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"golang.org/x/sys/unix"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
@@ -148,8 +150,7 @@ func (this *Manager) FindPolicy(policyId int64) *serverconfigs.HTTPCachePolicy {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
p, _ := this.policyMap[policyId]
|
||||
return p
|
||||
return this.policyMap[policyId]
|
||||
}
|
||||
|
||||
// FindStorageWithPolicy 根据策略ID查找存储
|
||||
@@ -157,8 +158,7 @@ func (this *Manager) FindStorageWithPolicy(policyId int64) StorageInterface {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
storage, _ := this.storageMap[policyId]
|
||||
return storage
|
||||
return this.storageMap[policyId]
|
||||
}
|
||||
|
||||
// NewStorageWithPolicy 根据策略获取存储对象
|
||||
@@ -178,8 +178,30 @@ func (this *Manager) TotalDiskSize() int64 {
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
var total = int64(0)
|
||||
var sidMap = map[string]bool{} // partition sid => bool
|
||||
for _, storage := range this.storageMap {
|
||||
total += storage.TotalDiskSize()
|
||||
// 这里不能直接用 storage.TotalDiskSize() 相加,因为多个缓存策略缓存目录可能处在同一个分区目录下
|
||||
fileStorage, ok := storage.(*FileStorage)
|
||||
if ok {
|
||||
var options = fileStorage.options // copy
|
||||
if options != nil {
|
||||
var dir = options.Dir // copy
|
||||
if len(dir) == 0 {
|
||||
continue
|
||||
}
|
||||
var stat = &unix.Statfs_t{}
|
||||
err := unix.Statfs(dir, stat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var sid = fmt.Sprintf("%d_%d", stat.Fsid.Val[0], stat.Fsid.Val[1])
|
||||
if sidMap[sid] {
|
||||
continue
|
||||
}
|
||||
sidMap[sid] = true
|
||||
total += int64(stat.Blocks-stat.Bfree) * int64(stat.Bsize) // we add extra int64() for darwin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total < 0 {
|
||||
@@ -234,3 +256,19 @@ func (this *Manager) FindAllStorages() []StorageInterface {
|
||||
}
|
||||
return storages
|
||||
}
|
||||
|
||||
// ScanGarbageCaches 清理目录中“失联”的缓存文件
|
||||
func (this *Manager) ScanGarbageCaches(callback func(path string) error) error {
|
||||
var storages = this.FindAllStorages()
|
||||
for _, storage := range storages {
|
||||
fileStorage, ok := storage.(*FileStorage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err := fileStorage.ScanGarbageCaches(callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
modeSlow int32 = 1
|
||||
modeFast int32 = 2
|
||||
)
|
||||
|
||||
// MaxOpenFiles max open files manager
|
||||
type MaxOpenFiles struct {
|
||||
ticker *time.Ticker
|
||||
mode int32
|
||||
}
|
||||
|
||||
func NewMaxOpenFiles() *MaxOpenFiles {
|
||||
var f = &MaxOpenFiles{}
|
||||
f.ticker = time.NewTicker(1 * time.Second)
|
||||
f.init()
|
||||
return f
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) init() {
|
||||
goman.New(func() {
|
||||
for range this.ticker.C {
|
||||
// reset mode
|
||||
atomic.StoreInt32(&this.mode, modeFast)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Fast() {
|
||||
atomic.AddInt32(&this.mode, modeFast)
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) FinishAll() {
|
||||
this.Fast()
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Slow() {
|
||||
atomic.StoreInt32(&this.mode, modeSlow)
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Next() bool {
|
||||
return atomic.LoadInt32(&this.mode) != modeSlow
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// 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"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMaxOpenFiles(t *testing.T) {
|
||||
var maxOpenFiles = caches.NewMaxOpenFiles()
|
||||
maxOpenFiles.Fast()
|
||||
t.Log("fast:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
time.Sleep(1*time.Second + 1*time.Millisecond)
|
||||
t.Log("slow 1 second:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log("slow 1 second:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Fast()
|
||||
t.Log("fast:", maxOpenFiles.Next())
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package caches
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
@@ -19,7 +20,7 @@ type PartialFileReader struct {
|
||||
func NewPartialFileReader(fp *os.File) *PartialFileReader {
|
||||
return &PartialFileReader{
|
||||
FileReader: NewFileReader(fp),
|
||||
rangePath: partialRangesFilePath(fp.Name()),
|
||||
rangePath: PartialRangesFilePath(fp.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
// 读取Range
|
||||
ranges, err := NewPartialRangesFromFile(this.rangePath)
|
||||
if err != nil {
|
||||
return errors.New("read ranges failed: " + err.Error())
|
||||
return fmt.Errorf("read ranges failed: %w", err)
|
||||
}
|
||||
this.ranges = ranges
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
@@ -22,6 +23,8 @@ import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -55,17 +58,14 @@ const (
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileTmpSuffix = ".tmp"
|
||||
MinDiskSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间
|
||||
HashKeyLength = 32
|
||||
)
|
||||
|
||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||
var sharedWritingFileKeyLocker = sync.Mutex{}
|
||||
|
||||
var maxOpenFiles = NewMaxOpenFiles()
|
||||
|
||||
const maxOpenFilesSlowCost = 1000 * time.Microsecond // us
|
||||
const protectingLoadWhenDump = false
|
||||
|
||||
// FileStorage 文件缓存
|
||||
//
|
||||
// 文件结构:
|
||||
@@ -162,6 +162,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
IsFull: false,
|
||||
})
|
||||
}
|
||||
this.subDirs = subDirs
|
||||
this.checkDiskSpace()
|
||||
|
||||
err = newOptions.Init()
|
||||
@@ -262,7 +263,7 @@ func (this *FileStorage) Init() error {
|
||||
} else {
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return errors.New("[CACHE]can not create dir:" + err.Error())
|
||||
return fmt.Errorf("[CACHE]can not create dir: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +303,9 @@ func (this *FileStorage) Init() error {
|
||||
// 检查磁盘空间
|
||||
this.checkDiskSpace()
|
||||
|
||||
// clean *.trash directories
|
||||
this.cleanAllDeletedDirs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -424,7 +428,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
maxMemorySize = maxSize
|
||||
}
|
||||
var memoryStorage = this.memoryStorage
|
||||
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
if !fsutils.DiskIsExtremelyFast() && !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||
if err == nil {
|
||||
return writer, nil
|
||||
@@ -445,9 +449,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return nil, ErrFileIsWriting
|
||||
}
|
||||
|
||||
if !isFlushing && !maxOpenFiles.Next() {
|
||||
if !isFlushing && !fsutils.WriteReady() {
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
return nil, ErrTooManyOpenFiles
|
||||
return nil, ErrServerIsBusy
|
||||
}
|
||||
|
||||
sharedWritingFileKeyMap[key] = zero.New()
|
||||
@@ -456,9 +460,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if !isOk {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}
|
||||
}()
|
||||
@@ -490,7 +491,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
stat, err := os.Stat(cachePath)
|
||||
|
||||
// 检查两次写入缓存的时间是否过于相近,分片内容不受此限制
|
||||
if err == nil && !isPartial && time.Now().Sub(stat.ModTime()) <= 1*time.Second {
|
||||
if err == nil && !isPartial && time.Since(stat.ModTime()) <= 1*time.Second {
|
||||
// 防止并发连续写入
|
||||
return nil, ErrFileIsWriting
|
||||
}
|
||||
@@ -558,7 +559,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if isNewCreated && existsFile {
|
||||
flags |= os.O_TRUNC
|
||||
}
|
||||
var before = time.Now()
|
||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||
if err != nil {
|
||||
// TODO 检查在各个系统中的稳定性
|
||||
@@ -572,13 +572,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !isFlushing {
|
||||
if time.Since(before) >= maxOpenFilesSlowCost {
|
||||
maxOpenFiles.Slow()
|
||||
} else {
|
||||
maxOpenFiles.Fast()
|
||||
}
|
||||
}
|
||||
|
||||
var removeOnFailure = true
|
||||
defer func() {
|
||||
@@ -628,7 +621,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
metaBodySize = bodySize
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_, err = writer.Write(metaBytes)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -639,18 +634,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return NewPartialFileWriter(writer, key, expiredAt, metaHeaderSize, metaBodySize, isNewCreated, isPartial, partialBodyOffset, partialRanges, func() {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}), nil
|
||||
} else {
|
||||
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, maxSize, func() {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}), nil
|
||||
}
|
||||
@@ -764,7 +753,7 @@ func (this *FileStorage) CleanAll() error {
|
||||
return err
|
||||
}
|
||||
for _, info := range subDirs {
|
||||
subDir := info.Name()
|
||||
var subDir = info.Name()
|
||||
|
||||
// 检查目录名
|
||||
if !dirNameReg.MatchString(subDir) {
|
||||
@@ -772,7 +761,7 @@ func (this *FileStorage) CleanAll() error {
|
||||
}
|
||||
|
||||
// 修改目录名
|
||||
tmpDir := dir + "/" + subDir + "-deleted"
|
||||
var tmpDir = dir + "/" + subDir + "." + timeutil.Format("YmdHis") + ".trash"
|
||||
err = os.Rename(dir+"/"+subDir, tmpDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -783,7 +772,11 @@ func (this *FileStorage) CleanAll() error {
|
||||
goman.New(func() {
|
||||
err = this.cleanDeletedDirs(dir)
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
|
||||
remotelogs.Warn("CACHE", "delete '*.trash' dirs failed: "+err.Error())
|
||||
} else {
|
||||
// try to clean again, to delete writing files when deleting
|
||||
time.Sleep(10 * time.Minute)
|
||||
_ = this.cleanDeletedDirs(dir)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -900,7 +893,7 @@ func (this *FileStorage) Stop() {
|
||||
|
||||
// TotalDiskSize 消耗的磁盘尺寸
|
||||
func (this *FileStorage) TotalDiskSize() int64 {
|
||||
stat, err := fsutils.StatCache(this.options.Dir)
|
||||
stat, err := fsutils.StatDeviceCache(this.options.Dir)
|
||||
if err == nil {
|
||||
return int64(stat.UsedSize())
|
||||
}
|
||||
@@ -940,7 +933,7 @@ func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFu
|
||||
|
||||
// 获取Hash对应的文件路径
|
||||
func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
|
||||
if len(hash) != 32 {
|
||||
if len(hash) != HashKeyLength {
|
||||
return "", false
|
||||
}
|
||||
var dir string
|
||||
@@ -1004,28 +997,23 @@ func (this *FileStorage) initList() error {
|
||||
// 清理任务
|
||||
// TODO purge每个分区
|
||||
func (this *FileStorage) purgeLoop() {
|
||||
// 检查磁盘剩余空间
|
||||
this.checkDiskSpace()
|
||||
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
var startLFU = false
|
||||
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
|
||||
var hasFullDisk = this.mainDiskIsFull
|
||||
if !hasFullDisk {
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
if subDir.IsFull {
|
||||
hasFullDisk = true
|
||||
break
|
||||
// 2TB级别以上
|
||||
if capacityBytes>>30 > 2000 {
|
||||
lfuFreePercent = 100 /** GB **/ / float32(capacityBytes>>30) * 100 /** % **/
|
||||
if lfuFreePercent > 3 {
|
||||
lfuFreePercent = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasFullDisk = this.hasFullDisk()
|
||||
if hasFullDisk {
|
||||
startLFU = true
|
||||
} else {
|
||||
@@ -1044,8 +1032,15 @@ func (this *FileStorage) purgeLoop() {
|
||||
var times = 1
|
||||
|
||||
// 空闲时间多清理
|
||||
if utils.SharedFreeHoursManager.IsFreeHour() {
|
||||
times = 5
|
||||
systemLoad, _ := load.Avg()
|
||||
if systemLoad != nil {
|
||||
if systemLoad.Load5 < 2 {
|
||||
times = 5
|
||||
} else if systemLoad.Load5 < 3 {
|
||||
times = 3
|
||||
} else if systemLoad.Load5 < 5 {
|
||||
times = 2
|
||||
}
|
||||
}
|
||||
|
||||
// 处于LFU阈值时,多清理
|
||||
@@ -1082,13 +1077,32 @@ func (this *FileStorage) purgeLoop() {
|
||||
|
||||
// 磁盘空间不足时,清除老旧的缓存
|
||||
if startLFU {
|
||||
var maxCount = 2000
|
||||
var maxLoops = 5
|
||||
|
||||
if fsutils.DiskIsFast() {
|
||||
maxCount = 5000
|
||||
} else if fsutils.DiskIsExtremelyFast() {
|
||||
maxCount = 10000
|
||||
}
|
||||
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count > 0 {
|
||||
for {
|
||||
maxLoops--
|
||||
if maxLoops <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 开始清理
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > 2000 {
|
||||
count = 2000
|
||||
if count > maxCount {
|
||||
count = maxCount
|
||||
}
|
||||
|
||||
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
||||
@@ -1104,6 +1118,11 @@ func (this *FileStorage) purgeLoop() {
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge file storage in LFU failed: "+err.Error())
|
||||
}
|
||||
|
||||
// 检查硬盘空间状态
|
||||
if !this.hasFullDisk() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1230,8 +1249,9 @@ func (this *FileStorage) hotLoop() {
|
||||
|
||||
func (this *FileStorage) diskCapacityBytes() int64 {
|
||||
var c1 = this.policy.CapacityBytes()
|
||||
if SharedManager.MaxDiskCapacity != nil {
|
||||
var c2 = SharedManager.MaxDiskCapacity.Bytes()
|
||||
var nodeCapacity = SharedManager.MaxDiskCapacity // copy
|
||||
if nodeCapacity != nil {
|
||||
var c2 = nodeCapacity.Bytes()
|
||||
if c2 > 0 {
|
||||
return c2
|
||||
}
|
||||
@@ -1239,7 +1259,25 @@ func (this *FileStorage) diskCapacityBytes() int64 {
|
||||
return c1
|
||||
}
|
||||
|
||||
// 清理 *-deleted 目录
|
||||
// remove all *.trash directories under policy directory
|
||||
func (this *FileStorage) cleanAllDeletedDirs() {
|
||||
var rootDirs = []string{this.options.Dir}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
if len(subDirs) > 0 {
|
||||
for _, subDir := range subDirs {
|
||||
rootDirs = append(rootDirs, subDir.Path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rootDir := range rootDirs {
|
||||
var dir = rootDir + "/p" + types.String(this.policy.Id)
|
||||
goman.New(func() {
|
||||
_ = this.cleanDeletedDirs(dir)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 清理 *.trash 目录
|
||||
// 由于在很多硬盘上耗时非常久,所以应该放在后台运行
|
||||
func (this *FileStorage) cleanDeletedDirs(dir string) error {
|
||||
fp, err := os.Open(dir)
|
||||
@@ -1254,8 +1292,8 @@ func (this *FileStorage) cleanDeletedDirs(dir string) error {
|
||||
return err
|
||||
}
|
||||
for _, info := range subDirs {
|
||||
subDir := info.Name()
|
||||
if !strings.HasSuffix(subDir, "-deleted") {
|
||||
var subDir = info.Name()
|
||||
if !strings.HasSuffix(subDir, ".trash") {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1295,13 +1333,6 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||
this.hotMapLocker.Lock()
|
||||
hotItem, ok := this.hotMap[key]
|
||||
|
||||
// 限制热点数据存活时间
|
||||
var unixTime = time.Now().Unix()
|
||||
var expiresAt = reader.ExpiresAt()
|
||||
if expiresAt <= 0 || expiresAt > unixTime+HotItemLifeSeconds {
|
||||
expiresAt = unixTime + HotItemLifeSeconds
|
||||
}
|
||||
|
||||
if ok {
|
||||
hotItem.Hits++
|
||||
} else if len(this.hotMap) < HotItemSize { // 控制数量
|
||||
@@ -1327,7 +1358,7 @@ func (this *FileStorage) removeCacheFile(path string) error {
|
||||
err = nil
|
||||
|
||||
// 删除Partial相关
|
||||
var partialPath = partialRangesFilePath(path)
|
||||
var partialPath = PartialRangesFilePath(path)
|
||||
if openFileCache != nil {
|
||||
openFileCache.Close(partialPath)
|
||||
}
|
||||
@@ -1410,7 +1441,7 @@ func (this *FileStorage) initOpenFileCache() {
|
||||
}
|
||||
|
||||
func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStorage)) {
|
||||
var memoryStorage = this.memoryStorage
|
||||
var memoryStorage = this.memoryStorage // copy
|
||||
if memoryStorage != nil {
|
||||
f(memoryStorage)
|
||||
}
|
||||
@@ -1418,21 +1449,63 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
||||
|
||||
// 检查磁盘剩余空间
|
||||
func (this *FileStorage) checkDiskSpace() {
|
||||
if this.options != nil && len(this.options.Dir) > 0 {
|
||||
stat, err := fsutils.Stat(this.options.Dir)
|
||||
var minFreeSize = DefaultMinDiskFreeSpace
|
||||
|
||||
var options = this.options // copy
|
||||
if options != nil && options.MinFreeSize != nil && options.MinFreeSize.Bytes() > 0 {
|
||||
minFreeSize = uint64(options.MinFreeSize.Bytes())
|
||||
}
|
||||
|
||||
if options != nil && len(options.Dir) > 0 {
|
||||
stat, err := fsutils.StatDevice(options.Dir)
|
||||
if err == nil {
|
||||
this.mainDiskIsFull = stat.FreeSize() < MinDiskSpace
|
||||
this.mainDiskIsFull = stat.FreeSize() < minFreeSize
|
||||
|
||||
// check capacity (only on main directory) when node capacity had not been set
|
||||
if !this.mainDiskIsFull {
|
||||
var capacityBytes int64
|
||||
var maxDiskCapacity = SharedManager.MaxDiskCapacity // copy
|
||||
if maxDiskCapacity != nil && maxDiskCapacity.Bytes() > 0 {
|
||||
capacityBytes = SharedManager.MaxDiskCapacity.Bytes()
|
||||
} else {
|
||||
var policy = this.policy // copy
|
||||
if policy != nil {
|
||||
capacityBytes = policy.CapacityBytes() // copy
|
||||
}
|
||||
}
|
||||
|
||||
if capacityBytes > 0 && stat.UsedSize() >= uint64(capacityBytes) {
|
||||
this.mainDiskIsFull = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
stat, err := fsutils.Stat(subDir.Path)
|
||||
stat, err := fsutils.StatDevice(subDir.Path)
|
||||
if err == nil {
|
||||
subDir.IsFull = stat.FreeSize() < MinDiskSpace
|
||||
subDir.IsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有已满的磁盘分区
|
||||
func (this *FileStorage) hasFullDisk() bool {
|
||||
this.checkDiskSpace()
|
||||
|
||||
var hasFullDisk = this.mainDiskIsFull
|
||||
if !hasFullDisk {
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
if subDir.IsFull {
|
||||
hasFullDisk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasFullDisk
|
||||
}
|
||||
|
||||
// 获取目录
|
||||
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
|
||||
@@ -1462,6 +1535,89 @@ func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||
return subDir.Path + suffix, subDir.IsFull
|
||||
}
|
||||
|
||||
// ScanGarbageCaches 清理目录中“失联”的缓存文件
|
||||
// “失联”为不在HashMap中的文件
|
||||
func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error) error {
|
||||
if !this.list.(*FileList).HashMapIsLoaded() {
|
||||
return errors.New("cache list is loading")
|
||||
}
|
||||
|
||||
var mainDir = this.options.Dir
|
||||
var allDirs = []string{mainDir}
|
||||
var subDirs = this.subDirs // copy
|
||||
for _, subDir := range subDirs {
|
||||
allDirs = append(allDirs, subDir.Path)
|
||||
}
|
||||
|
||||
for _, subDir := range allDirs {
|
||||
var dir0 = subDir + "/p" + types.String(this.policy.Id)
|
||||
dir1Matches, err := filepath.Glob(dir0 + "/*")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dir1 := range dir1Matches {
|
||||
if len(filepath.Base(dir1)) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
dir2Matches, err := filepath.Glob(dir1 + "/*")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
for _, dir2 := range dir2Matches {
|
||||
if len(filepath.Base(dir2)) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
fileMatches, err := filepath.Glob(dir2 + "/*.cache")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range fileMatches {
|
||||
var filename = filepath.Base(file)
|
||||
var hash = strings.TrimSuffix(filename, ".cache")
|
||||
if len(hash) != HashKeyLength {
|
||||
continue
|
||||
}
|
||||
|
||||
isReady, found := this.list.(*FileList).ExistQuick(hash)
|
||||
if !isReady {
|
||||
continue
|
||||
}
|
||||
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查文件正在被写入
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if fasttime.Now().Unix()-stat.ModTime().Unix() < 300 /** 5 minutes **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
if fileCallback != nil {
|
||||
err = fileCallback(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算字节数字代号
|
||||
func (this *FileStorage) charCode(r byte) uint8 {
|
||||
if r >= '0' && r <= '9' {
|
||||
return r - '0'
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
@@ -159,7 +160,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
t.Log(writer)
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: 200,
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{
|
||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
||||
"Last-Modified": []string{"Wed, 06 Jan 2021 10:03:29 GMT"},
|
||||
@@ -577,6 +578,31 @@ func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
||||
}
|
||||
|
||||
func TestFileStorage_ScanGarbageCaches(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 43,
|
||||
Options: map[string]any{"dir": "/Users/WorkSpace/EdgeProject/EdgeCache"},
|
||||
})
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = storage.ScanGarbageCaches(func(path string) {
|
||||
t.Log(path)
|
||||
}, func(path string) error {
|
||||
t.Log(path, PartialRangesFilePath(path))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFileStorage_Read(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
@@ -136,17 +136,6 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
// 增加点击量
|
||||
// 1/1000采样
|
||||
// TODO 考虑是否在缓存策略里设置
|
||||
if rands.Int(0, 1000) == 0 {
|
||||
var hitErr = this.list.IncreaseHit(types.String(hash))
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return reader, nil
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
@@ -483,15 +472,14 @@ func (this *MemoryStorage) startFlush() {
|
||||
if statCount == 100 {
|
||||
statCount = 0
|
||||
|
||||
if protectingLoadWhenDump {
|
||||
// delay some time to reduce load if needed
|
||||
if !fsutils.DiskIsFast() {
|
||||
loadStat, err := load.Avg()
|
||||
if err == nil && loadStat != nil {
|
||||
if loadStat.Load1 > 10 {
|
||||
writeDelayMS = 100
|
||||
} else if loadStat.Load1 > 3 {
|
||||
} else if loadStat.Load1 > 5 {
|
||||
writeDelayMS = 50
|
||||
} else if loadStat.Load1 > 2 {
|
||||
writeDelayMS = 10
|
||||
} else {
|
||||
writeDelayMS = 0
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ package caches
|
||||
|
||||
import "strings"
|
||||
|
||||
// 获取 ranges 文件路径
|
||||
func partialRangesFilePath(path string) string {
|
||||
// PartialRangesFilePath 获取 ranges 文件路径
|
||||
func PartialRangesFilePath(path string) string {
|
||||
// ranges路径
|
||||
var dotIndex = strings.LastIndex(path, ".")
|
||||
var rangePath = ""
|
||||
var rangePath string
|
||||
if dotIndex < 0 {
|
||||
rangePath = path + "@ranges.cache"
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestParseHost(t *testing.T) {
|
||||
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))
|
||||
t.Logf("%d", 1234567890123)
|
||||
}
|
||||
|
||||
func BenchmarkUint_String(b *testing.B) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package caches
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"os"
|
||||
@@ -42,7 +43,9 @@ func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, exp
|
||||
|
||||
// WriteHeader 写入数据
|
||||
func (this *FileWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.headerSize += int64(n)
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
@@ -72,21 +75,30 @@ func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
||||
|
||||
// Write 写入数据
|
||||
func (this *FileWriter) Write(data []byte) (n int, err error) {
|
||||
n, err = this.rawWriter.Write(data)
|
||||
this.bodySize += int64(n)
|
||||
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
|
||||
if this.storage != nil {
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
// split LARGE data
|
||||
var l = len(data)
|
||||
if l > (2 << 20) {
|
||||
var offset = 0
|
||||
const bufferSize = 256 << 10
|
||||
for {
|
||||
var end = offset + bufferSize
|
||||
if end > l {
|
||||
end = l
|
||||
}
|
||||
n1, err1 := this.write(data[offset:end])
|
||||
n += n1
|
||||
if err1 != nil {
|
||||
return n, err1
|
||||
}
|
||||
if end >= l {
|
||||
return n, nil
|
||||
}
|
||||
offset = end
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
}
|
||||
return
|
||||
// write NORMAL size data
|
||||
return this.write(data)
|
||||
}
|
||||
|
||||
// WriteAt 在指定位置写入数据
|
||||
@@ -126,18 +138,24 @@ func (this *FileWriter) Close() error {
|
||||
|
||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
_ = os.Remove(path)
|
||||
return err
|
||||
}
|
||||
err = this.WriteBodyLength(this.bodySize)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
_ = os.Remove(path)
|
||||
return err
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
err = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
} else if strings.HasSuffix(path, FileTmpSuffix) {
|
||||
@@ -156,7 +174,9 @@ func (this *FileWriter) Discard() error {
|
||||
this.endFunc()
|
||||
})
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
err := os.Remove(this.rawWriter.Name())
|
||||
return err
|
||||
@@ -182,3 +202,23 @@ func (this *FileWriter) Key() string {
|
||||
func (this *FileWriter) ItemType() ItemType {
|
||||
return ItemTypeFile
|
||||
}
|
||||
|
||||
func (this *FileWriter) write(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.bodySize += int64(n)
|
||||
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
|
||||
if this.storage != nil {
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package caches
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"os"
|
||||
@@ -42,7 +43,7 @@ func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaH
|
||||
isPartial: isPartial,
|
||||
bodyOffset: bodyOffset,
|
||||
ranges: ranges,
|
||||
rangePath: partialRangesFilePath(rawWriter.Name()),
|
||||
rangePath: PartialRangesFilePath(rawWriter.Name()),
|
||||
metaHeaderSize: metaHeaderSize,
|
||||
metaBodySize: metaBodySize,
|
||||
}
|
||||
@@ -53,7 +54,9 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
if !this.isNew {
|
||||
return
|
||||
}
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.headerSize += int64(n)
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
@@ -62,7 +65,9 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (this *PartialFileWriter) AppendHeader(data []byte) error {
|
||||
fsutils.WriteBegin()
|
||||
_, err := this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
} else {
|
||||
@@ -99,7 +104,9 @@ func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
||||
|
||||
// Write 写入数据
|
||||
func (this *PartialFileWriter) Write(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.bodySize += int64(n)
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
@@ -128,7 +135,9 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
||||
this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -172,7 +181,9 @@ func (this *PartialFileWriter) Close() error {
|
||||
this.ranges.BodySize = this.bodySize
|
||||
err := this.ranges.WriteToFile(this.rangePath)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
this.remove()
|
||||
return err
|
||||
}
|
||||
@@ -181,19 +192,25 @@ func (this *PartialFileWriter) Close() error {
|
||||
if this.isNew {
|
||||
err = this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
this.remove()
|
||||
return err
|
||||
}
|
||||
err = this.WriteBodyLength(this.bodySize)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
this.remove()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
err = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
this.remove()
|
||||
}
|
||||
@@ -207,7 +224,9 @@ func (this *PartialFileWriter) Discard() error {
|
||||
this.endFunc()
|
||||
})
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
_ = os.Remove(this.rangePath)
|
||||
|
||||
|
||||
@@ -1,38 +1,81 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
)
|
||||
|
||||
// APIConfig 节点API配置
|
||||
const ConfigFileName = "api_node.yaml"
|
||||
const oldConfigFileName = "api.yaml"
|
||||
|
||||
type APIConfig struct {
|
||||
RPC struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
} `yaml:"rpc" json:"rpc"`
|
||||
NodeId string `yaml:"nodeId" json:"nodeId"`
|
||||
Secret string `yaml:"secret" json:"secret"`
|
||||
} `yaml:"rpc,omitempty" json:"rpc"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
NodeId string `yaml:"nodeId" json:"nodeId"`
|
||||
Secret string `yaml:"secret" json:"secret"`
|
||||
}
|
||||
|
||||
func NewAPIConfig() *APIConfig {
|
||||
return &APIConfig{}
|
||||
}
|
||||
|
||||
func (this *APIConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
if len(this.RPCEndpoints) == 0 {
|
||||
return errors.New("no valid 'rpc.endpoints'")
|
||||
}
|
||||
|
||||
if len(this.NodeId) == 0 {
|
||||
return errors.New("'nodeId' required")
|
||||
}
|
||||
if len(this.Secret) == 0 {
|
||||
return errors.New("'secret' required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
data, err := os.ReadFile(Tea.ConfigFile("api.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(filename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, errors.New("init error: " + err.Error())
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
if filename == oldConfigFileName {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
return nil, errors.New("no config file '" + ConfigFileName + "' found")
|
||||
}
|
||||
|
||||
// WriteFile 保存到文件
|
||||
|
||||
@@ -3,6 +3,7 @@ package configs_test
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -12,4 +13,10 @@ func TestLoadAPIConfig(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,57 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ClusterConfig 集群配置
|
||||
type ClusterConfig struct {
|
||||
RPC struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
} `yaml:"rpc" json:"rpc"`
|
||||
} `yaml:"rpc,omitempty" json:"rpc"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
|
||||
ClusterId string `yaml:"clusterId" json:"clusterId"`
|
||||
Secret string `yaml:"secret" json:"secret"`
|
||||
}
|
||||
|
||||
func (this *ClusterConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadClusterConfig() (*ClusterConfig, error) {
|
||||
for _, filename := range []string{"api_cluster.yaml", "cluster.yaml"} {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(filename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config = &ClusterConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
23
internal/configs/cluster_config_test.go
Normal file
23
internal/configs/cluster_config_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package configs_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadClusterConfig(t *testing.T) {
|
||||
config, err := configs.LoadClusterConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.2.6"
|
||||
Version = "1.2.9"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
|
||||
@@ -22,8 +22,6 @@ var (
|
||||
|
||||
IsQuiting = false // 是否正在退出
|
||||
EnableDBStat = false // 是否开启本地数据库统计
|
||||
|
||||
DiskIsFast = false // 是否为高速硬盘
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
|
||||
@@ -19,7 +19,6 @@ func TestAES128CFBMethod_Encrypt(t *testing.T) {
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src = make([]byte, len(src))
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -54,7 +53,6 @@ func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
a := []byte{1}
|
||||
_, err = method.Decrypt(a)
|
||||
if err != nil {
|
||||
@@ -64,7 +62,6 @@ func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
|
||||
for _, dst := range sources {
|
||||
dst2 := append([]byte{}, dst...)
|
||||
src2 := make([]byte, len(dst2))
|
||||
src2, err := method.Decrypt(dst2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -9,8 +9,8 @@ func TestOn(t *testing.T) {
|
||||
type User struct {
|
||||
name string
|
||||
}
|
||||
var u = &User{}
|
||||
var u2 = &User{}
|
||||
var u = &User{name: "lily"}
|
||||
var u2 = &User{name: "lucy"}
|
||||
|
||||
events.On("hello", func() {
|
||||
t.Log("world")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -92,7 +93,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
||||
// 对比配置
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return errors.New("encode config to json failed: " + err.Error())
|
||||
return fmt.Errorf("encode config to json failed: %w", err)
|
||||
}
|
||||
if !allowIPListChanged && bytes.Equal(this.lastConfig, configJSON) {
|
||||
return nil
|
||||
@@ -188,7 +189,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
for _, filter := range nftablesFilters {
|
||||
chain, oldRules, err := this.getRules(filter)
|
||||
if err != nil {
|
||||
return errors.New("get old rules failed: " + err.Error())
|
||||
return fmt.Errorf("get old rules failed: %w", err)
|
||||
}
|
||||
|
||||
var protocol = filter.protocol()
|
||||
@@ -273,7 +274,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
// 先清空所有相关规则
|
||||
err = this.removeOldTCPRules(chain, oldRules)
|
||||
if err != nil {
|
||||
return errors.New("delete old rules failed: " + err.Error())
|
||||
return fmt.Errorf("delete old rules failed: %w", err)
|
||||
}
|
||||
|
||||
// 添加新规则
|
||||
@@ -281,9 +282,9 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
if maxConnections > 0 {
|
||||
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)}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +294,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,14 +306,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
} else {
|
||||
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"}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,14 +326,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
} else {
|
||||
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"}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,11 +499,11 @@ func (this *DDoSProtectionManager) getTable(filter *nftablesTableDefinition) (*n
|
||||
func (this *DDoSProtectionManager) getRules(filter *nftablesTableDefinition) (*nftables.Chain, []*nftables.Rule, error) {
|
||||
table, err := this.getTable(filter)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("get table failed: " + err.Error())
|
||||
return nil, nil, fmt.Errorf("get table failed: %w", err)
|
||||
}
|
||||
chain, err := table.GetChain(nftablesChainName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("get chain failed: " + err.Error())
|
||||
return nil, nil, fmt.Errorf("get chain failed: %w", err)
|
||||
}
|
||||
rules, err := chain.GetRules()
|
||||
return chain, rules, err
|
||||
@@ -538,7 +539,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
// 不存在则删除
|
||||
err = set.DeleteIPElement(ip)
|
||||
if err != nil {
|
||||
return errors.New("delete ip element '" + ip + "' failed: " + err.Error())
|
||||
return fmt.Errorf("delete ip element '%s' failed: %w", ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +557,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
// 不存在则添加
|
||||
err = set.AddIPElement(ip, nil, false)
|
||||
if err != nil {
|
||||
return errors.New("add ip '" + ip + "' failed: " + err.Error())
|
||||
return fmt.Errorf("add ip '%s' failed: %w", ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
@@ -194,7 +194,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("run command failed '" + cmd.String() + "': " + err.Error())
|
||||
return fmt.Errorf("run command failed '%s': %w", cmd.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -149,10 +150,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
table, err = this.conn.AddIPv6Table(tableDef.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("create table '" + tableDef.Name + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create table '%s' failed: %w", tableDef.Name, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get table '" + tableDef.Name + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get table '%s' failed: %w", tableDef.Name, err)
|
||||
}
|
||||
}
|
||||
if table == nil {
|
||||
@@ -166,10 +167,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
if nftables.IsNotFound(err) {
|
||||
chain, err = table.AddAcceptChain(chainName)
|
||||
if err != nil {
|
||||
return errors.New("create chain '" + chainName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create chain '%s' failed: %w", chainName, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get chain '" + chainName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get chain '%s' failed: %w", chainName, err)
|
||||
}
|
||||
}
|
||||
if chain == nil {
|
||||
@@ -184,7 +185,7 @@ func (this *NFTablesFirewall) init() error {
|
||||
_, err = chain.AddAcceptInterfaceRule("lo", loRuleName)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("add 'lo' rule failed: " + err.Error())
|
||||
return fmt.Errorf("add 'lo' rule failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,10 +208,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
HasTimeout: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("create set '" + setName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create set '%s' failed: %w", setName, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get set '" + setName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get set '%s' failed: %w", setName, err)
|
||||
}
|
||||
}
|
||||
if set == nil {
|
||||
@@ -259,10 +260,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("add rule failed: " + err.Error())
|
||||
return fmt.Errorf("add rule failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get rule failed: " + err.Error())
|
||||
return fmt.Errorf("get rule failed: %w", err)
|
||||
}
|
||||
}
|
||||
if rule == nil {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -85,7 +85,7 @@ func (this *Installer) Install() error {
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if len(NftExePath()) > 0 {
|
||||
if len(NftExePath()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func (this *Installer) Install() error {
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ": " + cmd.Stderr())
|
||||
return fmt.Errorf("%w: %s", err, cmd.Stderr())
|
||||
}
|
||||
|
||||
remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully")
|
||||
|
||||
@@ -147,7 +147,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("%w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
@@ -70,7 +71,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if err != nil {
|
||||
var output = cmd.Stderr()
|
||||
if !strings.Contains(output, "already exists") {
|
||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -88,7 +89,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if err != nil {
|
||||
var output = cmd.Stderr()
|
||||
if !strings.Contains(output, "already exists") {
|
||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -116,7 +117,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if strings.Contains(output, "NAME_CONFLICT") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +135,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if strings.Contains(output, "NAME_CONFLICT") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +149,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd reload: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +201,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +222,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +284,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
return nil
|
||||
}
|
||||
|
||||
var listName = ""
|
||||
var listName string
|
||||
var isIPv6 = strings.Contains(item.IpFrom, ":")
|
||||
|
||||
switch listType {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
@@ -128,7 +128,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
||||
if strings.Contains(output, "No chain/target/match") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New(err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("%w, output: %s", err, output)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -102,7 +102,7 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
|
||||
continue
|
||||
}
|
||||
|
||||
instances, _ := this.eventMap[action.EventLevel]
|
||||
var instances = this.eventMap[action.EventLevel]
|
||||
instances = append(instances, instance)
|
||||
this.eventMap[action.EventLevel] = instances
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
@@ -62,7 +61,7 @@ func (this *ScriptAction) runAction(action string, listType IPListType, item *pb
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("%w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
@@ -14,6 +15,8 @@ var GlobalWhiteIPList = NewIPList()
|
||||
// IPList IP名单
|
||||
// TODO IP名单可以分片关闭,这样让每一片的数据量减少,查询更快
|
||||
type IPList struct {
|
||||
isDeleted bool
|
||||
|
||||
itemsMap map[uint64]*IPItem // id => item
|
||||
sortedItems []*IPItem
|
||||
allItemsMap map[uint64]*IPItem // id => item
|
||||
@@ -38,11 +41,19 @@ func NewIPList() *IPList {
|
||||
}
|
||||
|
||||
func (this *IPList) Add(item *IPItem) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
this.addItem(item, true)
|
||||
}
|
||||
|
||||
// AddDelay 延迟添加,需要手工调用Sort()函数
|
||||
func (this *IPList) AddDelay(item *IPItem) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
this.addItem(item, false)
|
||||
}
|
||||
|
||||
@@ -60,6 +71,10 @@ func (this *IPList) Delete(itemId uint64) {
|
||||
|
||||
// Contains 判断是否包含某个IP
|
||||
func (this *IPList) Contains(ip uint64) bool {
|
||||
if this.isDeleted {
|
||||
return false
|
||||
}
|
||||
|
||||
this.locker.RLock()
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
@@ -75,6 +90,10 @@ func (this *IPList) Contains(ip uint64) bool {
|
||||
|
||||
// ContainsExpires 判断是否包含某个IP
|
||||
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.RLock()
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
@@ -94,6 +113,10 @@ func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||
|
||||
// ContainsIPStrings 是否包含一组IP中的任意一个,并返回匹配的第一个Item
|
||||
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ipStrings) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -125,6 +148,11 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
|
||||
return
|
||||
}
|
||||
|
||||
func (this *IPList) SetDeleted() {
|
||||
this.isDeleted = true
|
||||
logs.Println("set deleted:", this.isDeleted) // TODO
|
||||
}
|
||||
|
||||
func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
if item == nil {
|
||||
return
|
||||
|
||||
@@ -22,10 +22,10 @@ func TestIPListDB_AddItem(t *testing.T) {
|
||||
IpFrom: "192.168.1.101",
|
||||
IpTo: "",
|
||||
Version: 1024,
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
Reason: "",
|
||||
ListId: 2,
|
||||
IsDeleted: true,
|
||||
IsDeleted: false,
|
||||
Type: "ipv4",
|
||||
EventLevel: "error",
|
||||
ListType: "black",
|
||||
|
||||
@@ -187,7 +187,7 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
})
|
||||
if err != nil {
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Warn("IP_LIST_MANAGER", "rpc connection error: "+err.Error())
|
||||
remotelogs.Debug("IP_LIST_MANAGER", "rpc connection error: "+err.Error())
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
@@ -214,7 +214,7 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
|
||||
func (this *IPListManager) FindList(listId int64) *IPList {
|
||||
this.locker.Lock()
|
||||
list, _ := this.listMap[listId]
|
||||
var list = this.listMap[listId]
|
||||
this.locker.Unlock()
|
||||
return list
|
||||
}
|
||||
@@ -229,6 +229,11 @@ func (this *IPListManager) DeleteExpiredItems() {
|
||||
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
var changedLists = map[*IPList]zero.Zero{}
|
||||
for _, item := range items {
|
||||
// 调试
|
||||
if Tea.IsTesting() {
|
||||
this.debugItem(item)
|
||||
}
|
||||
|
||||
var list *IPList
|
||||
// TODO 实现节点专有List
|
||||
if item.ServerId > 0 { // 服务专有List
|
||||
@@ -300,3 +305,12 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调试IP信息
|
||||
func (this *IPListManager) debugItem(item *pb.IPItem) {
|
||||
if item.IsDeleted {
|
||||
remotelogs.Debug("IP_ITEM_DEBUG", "delete '"+item.IpFrom+"'")
|
||||
} else {
|
||||
remotelogs.Debug("IP_ITEM_DEBUG", "add '"+item.IpFrom+"'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ func init() {
|
||||
type Manager struct {
|
||||
isQuiting bool
|
||||
|
||||
tasks map[int64]*Task // itemId => *Task
|
||||
categoryTasks map[string][]*Task // category => []*Task
|
||||
locker sync.RWMutex
|
||||
taskMap map[int64]*Task // itemId => *Task
|
||||
categoryTaskMap map[string][]*Task // category => []*Task
|
||||
locker sync.RWMutex
|
||||
|
||||
hasHTTPMetrics bool
|
||||
hasTCPMetrics bool
|
||||
@@ -37,8 +37,8 @@ type Manager struct {
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
tasks: map[int64]*Task{},
|
||||
categoryTasks: map[string][]*Task{},
|
||||
taskMap: map[int64]*Task{},
|
||||
categoryTaskMap: map[string][]*Task{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
}
|
||||
|
||||
// 停用以前的 或 修改现在的
|
||||
for itemId, task := range this.tasks {
|
||||
for itemId, task := range this.taskMap {
|
||||
newItem, ok := newMap[itemId]
|
||||
if !ok || !newItem.IsOn { // 停用以前的
|
||||
remotelogs.Println("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"'")
|
||||
@@ -64,7 +64,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
|
||||
}
|
||||
delete(this.tasks, itemId)
|
||||
delete(this.taskMap, itemId)
|
||||
} else { // 更新已存在的
|
||||
if newItem.Version != task.item.Version {
|
||||
remotelogs.Println("METRIC_MANAGER", "update task '"+strconv.FormatInt(itemId, 10)+"'")
|
||||
@@ -78,7 +78,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
if !newItem.IsOn {
|
||||
continue
|
||||
}
|
||||
_, ok := this.tasks[newItem.Id]
|
||||
_, ok := this.taskMap[newItem.Id]
|
||||
if !ok {
|
||||
remotelogs.Println("METRIC_MANAGER", "start task '"+strconv.FormatInt(newItem.Id, 10)+"'")
|
||||
task := NewTask(newItem)
|
||||
@@ -92,7 +92,7 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
remotelogs.Error("METRIC_MANAGER", "start task failed: "+err.Error())
|
||||
continue
|
||||
}
|
||||
this.tasks[newItem.Id] = task
|
||||
this.taskMap[newItem.Id] = task
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +100,11 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
this.hasHTTPMetrics = false
|
||||
this.hasTCPMetrics = false
|
||||
this.hasUDPMetrics = false
|
||||
this.categoryTasks = map[string][]*Task{}
|
||||
for _, task := range this.tasks {
|
||||
tasks := this.categoryTasks[task.item.Category]
|
||||
this.categoryTaskMap = map[string][]*Task{}
|
||||
for _, task := range this.taskMap {
|
||||
var tasks = this.categoryTaskMap[task.item.Category]
|
||||
tasks = append(tasks, task)
|
||||
this.categoryTasks[task.item.Category] = tasks
|
||||
this.categoryTaskMap[task.item.Category] = tasks
|
||||
|
||||
switch task.item.Category {
|
||||
case serverconfigs.MetricItemCategoryHTTP:
|
||||
@@ -124,10 +124,12 @@ func (this *Manager) Add(obj MetricInterface) {
|
||||
}
|
||||
|
||||
this.locker.RLock()
|
||||
for _, task := range this.categoryTasks[obj.MetricCategory()] {
|
||||
var tasks = this.categoryTaskMap[obj.MetricCategory()]
|
||||
this.locker.RUnlock()
|
||||
|
||||
for _, task := range tasks {
|
||||
task.Add(obj)
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
}
|
||||
|
||||
func (this *Manager) HasHTTPMetrics() bool {
|
||||
@@ -149,9 +151,9 @@ func (this *Manager) Quit() {
|
||||
remotelogs.Println("METRIC_MANAGER", "quit")
|
||||
|
||||
this.locker.Lock()
|
||||
for _, task := range this.tasks {
|
||||
for _, task := range this.taskMap {
|
||||
_ = task.Stop()
|
||||
}
|
||||
this.tasks = map[int64]*Task{}
|
||||
this.taskMap = map[int64]*Task{}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestNewManager(t *testing.T) {
|
||||
var manager = NewManager()
|
||||
{
|
||||
manager.Update([]*serverconfigs.MetricItemConfig{})
|
||||
for _, task := range manager.tasks {
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log(task.item.Id)
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func TestNewManager(t *testing.T) {
|
||||
Id: 3,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.tasks {
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log("task:", task.item.Id)
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func TestNewManager(t *testing.T) {
|
||||
Id: 2,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.tasks {
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log("task:", task.item.Id)
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func TestNewManager(t *testing.T) {
|
||||
Version: 1,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.tasks {
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log("task:", task.item.Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ type Stat struct {
|
||||
}
|
||||
|
||||
func SumStat(serverId int64, keys []string, time string, version int32, itemId int64) string {
|
||||
keysData := strings.Join(keys, "$EDGE$")
|
||||
var keysData = strings.Join(keys, "$EDGE$")
|
||||
return strconv.FormatUint(fnv.HashString(strconv.FormatInt(serverId, 10)+"@"+keysData+"@"+time+"@"+strconv.Itoa(int(version))+"@"+strconv.FormatInt(itemId, 10)), 10)
|
||||
}
|
||||
|
||||
20
internal/metrics/sum_test.go
Normal file
20
internal/metrics/sum_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkSumStat(b *testing.B) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
metrics.SumStat(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -59,7 +60,7 @@ type Task struct {
|
||||
serverIdMapLocker sync.Mutex
|
||||
|
||||
statsMap map[string]*Stat // 待写入队列,hash => *Stat
|
||||
statsLocker sync.Mutex
|
||||
statsLocker sync.RWMutex
|
||||
statsTicker *utils.Ticker
|
||||
}
|
||||
|
||||
@@ -237,7 +238,7 @@ func (this *Task) Add(obj MetricInterface) {
|
||||
|
||||
var keys = []string{}
|
||||
for _, key := range this.item.Keys {
|
||||
k := obj.MetricKey(key)
|
||||
var k = obj.MetricKey(key)
|
||||
|
||||
// 忽略499状态
|
||||
if key == "${status}" && k == "499" {
|
||||
@@ -253,14 +254,19 @@ func (this *Task) Add(obj MetricInterface) {
|
||||
}
|
||||
|
||||
var hash = SumStat(obj.MetricServerId(), keys, this.item.CurrentTime(), this.item.Version, this.item.Id)
|
||||
this.statsLocker.Lock()
|
||||
var countItems int
|
||||
this.statsLocker.RLock()
|
||||
oldStat, ok := this.statsMap[hash]
|
||||
if !ok {
|
||||
countItems = len(this.statsMap)
|
||||
}
|
||||
this.statsLocker.RUnlock()
|
||||
if ok {
|
||||
oldStat.Value += v
|
||||
oldStat.Hash = hash
|
||||
atomic.AddInt64(&oldStat.Value, 1)
|
||||
} else {
|
||||
// 防止过载
|
||||
if len(this.statsMap) < MaxQueueSize {
|
||||
if countItems < MaxQueueSize {
|
||||
this.statsLocker.Lock()
|
||||
this.statsMap[hash] = &Stat{
|
||||
ServerId: obj.MetricServerId(),
|
||||
Keys: keys,
|
||||
@@ -268,9 +274,9 @@ func (this *Task) Add(obj MetricInterface) {
|
||||
Time: this.item.CurrentTime(),
|
||||
Hash: hash,
|
||||
}
|
||||
this.statsLocker.Unlock()
|
||||
}
|
||||
}
|
||||
this.statsLocker.Unlock()
|
||||
}
|
||||
|
||||
// Stop 停止任务
|
||||
|
||||
@@ -4,11 +4,15 @@ package metrics_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -213,3 +217,65 @@ func TestTask_Upload(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
var testingTask *metrics.Task
|
||||
var testingTaskInitOnce = &sync.Once{}
|
||||
|
||||
func initTestingTask() {
|
||||
testingTask = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "tcp",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
})
|
||||
|
||||
err := testingTask.Init()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = testingTask.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTask_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
testingTaskInitOnce.Do(func() {
|
||||
initTestingTask()
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
testingTask.Add(&taskRequest{})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type taskRequest struct {
|
||||
}
|
||||
|
||||
func (this *taskRequest) MetricKey(key string) string {
|
||||
return configutils.ParseVariables(key, func(varName string) (value string) {
|
||||
return "1.2.3.4"
|
||||
})
|
||||
}
|
||||
|
||||
func (this *taskRequest) MetricValue(value string) (result int64, ok bool) {
|
||||
return 1, true
|
||||
}
|
||||
|
||||
func (this *taskRequest) MetricServerId() int64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (this *taskRequest) MetricCategory() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
@@ -50,7 +50,11 @@ func (this *APIStream) Start() {
|
||||
}
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Warn("API_STREAM", err.Error())
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Debug("API_STREAM", err.Error())
|
||||
} else {
|
||||
remotelogs.Warn("API_STREAM", err.Error())
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -76,7 +80,7 @@ func (this *APIStream) loop() error {
|
||||
if this.isQuiting {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
return err
|
||||
}
|
||||
this.stream = nodeStream
|
||||
|
||||
@@ -92,7 +96,7 @@ func (this *APIStream) loop() error {
|
||||
remotelogs.Println("API_STREAM", "quit")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@@ -438,10 +442,10 @@ func (this *APIStream) handleChangeAPINode(message *pb.NodeStreamMessage) error
|
||||
return nil
|
||||
}
|
||||
|
||||
config.RPC.Endpoints = []string{messageData.Addr}
|
||||
config.RPCEndpoints = []string{messageData.Addr}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "save config file failed: "+err.Error())
|
||||
return nil
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
@@ -98,7 +98,7 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
this.lastErr = errors.New("read error: " + err.Error())
|
||||
this.lastErr = fmt.Errorf("read error: %w", err)
|
||||
} else {
|
||||
this.lastErr = nil
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
this.lastErr = errors.New("write error: " + err.Error())
|
||||
this.lastErr = fmt.Errorf("write error: %w", err)
|
||||
} else {
|
||||
this.lastErr = nil
|
||||
}
|
||||
@@ -188,6 +188,8 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
var before = time.Now()
|
||||
n, err = this.rawConn.Write(b)
|
||||
if n > 0 {
|
||||
atomic.AddInt64(&this.totalSentBytes, int64(n))
|
||||
|
||||
// 统计当前服务带宽
|
||||
if this.serverId > 0 {
|
||||
// TODO 需要加入在serverId绑定之前的带宽
|
||||
@@ -196,9 +198,9 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
|
||||
var cost = time.Since(before).Seconds()
|
||||
if cost > 1 {
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(float64(n)/cost), int64(n))
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.userPlanId, this.serverId, int64(float64(n)/cost), int64(n))
|
||||
} else {
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.serverId, int64(n), int64(n))
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(this.userId, this.userPlanId, this.serverId, int64(n), int64(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,7 @@ type BaseClientConn struct {
|
||||
|
||||
isBound bool
|
||||
userId int64
|
||||
userPlanId int64
|
||||
serverId int64
|
||||
remoteAddr string
|
||||
hasLimit bool
|
||||
@@ -25,6 +27,8 @@ type BaseClientConn struct {
|
||||
isClosed bool
|
||||
|
||||
rawIP string
|
||||
|
||||
totalSentBytes int64
|
||||
}
|
||||
|
||||
func (this *BaseClientConn) IsClosed() bool {
|
||||
@@ -103,11 +107,31 @@ func (this *BaseClientConn) SetUserId(userId int64) {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *BaseClientConn) SetUserPlanId(userPlanId int64) {
|
||||
this.userPlanId = userPlanId
|
||||
|
||||
// 设置包装前连接
|
||||
switch conn := this.rawConn.(type) {
|
||||
case *tls.Conn:
|
||||
nativeConn, ok := conn.NetConn().(ClientConnInterface)
|
||||
if ok {
|
||||
nativeConn.SetUserPlanId(userPlanId)
|
||||
}
|
||||
case *ClientConn:
|
||||
conn.SetUserPlanId(userPlanId)
|
||||
}
|
||||
}
|
||||
|
||||
// UserId 获取当前连接所属服务的用户ID
|
||||
func (this *BaseClientConn) UserId() int64 {
|
||||
return this.userId
|
||||
}
|
||||
|
||||
// UserPlanId 用户套餐ID
|
||||
func (this *BaseClientConn) UserPlanId() int64 {
|
||||
return this.userPlanId
|
||||
}
|
||||
|
||||
// RawIP 原本IP
|
||||
func (this *BaseClientConn) RawIP() string {
|
||||
if len(this.rawIP) > 0 {
|
||||
@@ -160,3 +184,10 @@ func (this *BaseClientConn) SetFingerprint(fingerprint []byte) {
|
||||
func (this *BaseClientConn) Fingerprint() []byte {
|
||||
return this.fingerprint
|
||||
}
|
||||
|
||||
// LastRequestBytes 读取上一次请求发送的字节数
|
||||
func (this *BaseClientConn) LastRequestBytes() int64 {
|
||||
var result = atomic.LoadInt64(&this.totalSentBytes)
|
||||
atomic.StoreInt64(&this.totalSentBytes, 0)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@ type ClientConnInterface interface {
|
||||
// SetServerId 设置服务ID
|
||||
SetServerId(serverId int64) (goNext bool)
|
||||
|
||||
// SetUserId 设置所属服务的用户ID
|
||||
// SetUserId 设置所属网站的用户ID
|
||||
SetUserId(userId int64)
|
||||
|
||||
// SetUserPlanId 设置
|
||||
SetUserPlanId(userPlanId int64)
|
||||
|
||||
// UserId 获取当前连接所属服务的用户ID
|
||||
UserId() int64
|
||||
|
||||
@@ -32,4 +35,7 @@ type ClientConnInterface interface {
|
||||
|
||||
// Fingerprint 读取指纹信息
|
||||
Fingerprint() []byte
|
||||
|
||||
// LastRequestBytes 读取上一次请求发送的字节数
|
||||
LastRequestBytes() int64
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (this *ClientListener) Accept() (net.Conn, error) {
|
||||
firewalls.DropTemporaryTo(ip, expiresAt)
|
||||
} else {
|
||||
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) {
|
||||
var ok = false
|
||||
var ok bool
|
||||
expiresAt, ok = waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
|
||||
if ok {
|
||||
canGoNext = false
|
||||
|
||||
@@ -82,3 +82,18 @@ func (this *ClientTLSConn) Fingerprint() []byte {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastRequestBytes 读取上一次请求发送的字节数
|
||||
func (this *ClientTLSConn) LastRequestBytes() int64 {
|
||||
tlsConn, ok := this.rawConn.(*tls.Conn)
|
||||
if ok {
|
||||
var rawConn = tlsConn.NetConn()
|
||||
if rawConn != nil {
|
||||
clientConn, ok := rawConn.(*ClientConn)
|
||||
if ok {
|
||||
return clientConn.LastRequestBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ func TestHTTPAccessLogQueue_Memory(t *testing.T) {
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
_ = accessLogs
|
||||
|
||||
// will not release automatically
|
||||
func() {
|
||||
@@ -131,6 +132,7 @@ func TestHTTPAccessLogQueue_Memory(t *testing.T) {
|
||||
RequestPath: "https://goedge.cn/hello/world",
|
||||
})
|
||||
}
|
||||
_ = accessLogs1
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -22,6 +24,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -42,9 +45,11 @@ var SharedHTTPCacheTaskManager = NewHTTPCacheTaskManager()
|
||||
// HTTPCacheTaskManager 缓存任务管理
|
||||
type HTTPCacheTaskManager struct {
|
||||
ticker *time.Ticker
|
||||
httpClient *http.Client
|
||||
protocolReg *regexp.Regexp
|
||||
|
||||
timeoutClientMap map[time.Duration]*http.Client // timeout seconds=> *http.Client
|
||||
locker sync.Mutex
|
||||
|
||||
taskQueue chan *pb.PurgeServerCacheRequest
|
||||
}
|
||||
|
||||
@@ -53,36 +58,12 @@ func NewHTTPCacheTaskManager() *HTTPCacheTaskManager {
|
||||
if Tea.IsTesting() {
|
||||
duration = 10 * time.Second
|
||||
}
|
||||
return &HTTPCacheTaskManager{
|
||||
ticker: time.NewTicker(duration),
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Minute, // TODO 可以设置请求超时时间
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.Dial(network, "127.0.0.1:"+port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connutils.NewNoStat(conn), nil
|
||||
},
|
||||
MaxIdleConns: 128,
|
||||
MaxIdleConnsPerHost: 32,
|
||||
MaxConnsPerHost: 32,
|
||||
IdleConnTimeout: 2 * time.Minute,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSHandshakeTimeout: 0,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
protocolReg: regexp.MustCompile(`^(?i)(http|https)://`),
|
||||
taskQueue: make(chan *pb.PurgeServerCacheRequest, 1024),
|
||||
return &HTTPCacheTaskManager{
|
||||
ticker: time.NewTicker(duration),
|
||||
protocolReg: regexp.MustCompile(`^(?i)(http|https)://`),
|
||||
taskQueue: make(chan *pb.PurgeServerCacheRequest, 1024),
|
||||
timeoutClientMap: make(map[time.Duration]*http.Client),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +214,6 @@ func (this *HTTPCacheTaskManager) processKey(key *pb.HTTPCacheTaskKey) error {
|
||||
}
|
||||
|
||||
// TODO 增加失败重试
|
||||
// TODO 使用并发操作
|
||||
func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error {
|
||||
var fullKey = key.Key
|
||||
if !this.protocolReg.MatchString(fullKey) {
|
||||
@@ -242,17 +222,17 @@ func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error {
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fullKey, nil)
|
||||
if err != nil {
|
||||
return errors.New("invalid url: " + fullKey + ": " + err.Error())
|
||||
return fmt.Errorf("invalid url: '%s': %w", fullKey, err)
|
||||
}
|
||||
|
||||
// TODO 可以在管理界面自定义Header
|
||||
req.Header.Set("X-Edge-Cache-Action", "fetch")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36") // TODO 可以定义
|
||||
req.Header.Set("Accept-Encoding", "gzip, deflate, br")
|
||||
resp, err := this.httpClient.Do(req)
|
||||
resp, err := this.httpClient().Do(req)
|
||||
if err != nil {
|
||||
err = this.simplifyErr(err)
|
||||
return errors.New("request failed: " + fullKey + ": " + err.Error())
|
||||
return fmt.Errorf("request failed: '%s': %w", fullKey, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -269,7 +249,7 @@ func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error {
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
err = this.simplifyErr(err)
|
||||
return errors.New("request failed: " + fullKey + ": " + err.Error())
|
||||
return fmt.Errorf("request failed: '%s': %w", fullKey, err)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -288,3 +268,57 @@ func (this *HTTPCacheTaskManager) simplifyErr(err error) error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *HTTPCacheTaskManager) httpClient() *http.Client {
|
||||
var timeout = serverconfigs.DefaultHTTPCachePolicyFetchTimeout
|
||||
|
||||
var nodeConfig = sharedNodeConfig // copy
|
||||
if nodeConfig != nil {
|
||||
var cachePolicies = nodeConfig.HTTPCachePolicies // copy
|
||||
if len(cachePolicies) > 0 && cachePolicies[0].FetchTimeout != nil && cachePolicies[0].FetchTimeout.Count > 0 {
|
||||
var fetchTimeout = cachePolicies[0].FetchTimeout.Duration()
|
||||
if fetchTimeout > 0 {
|
||||
timeout = fetchTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
client, ok := this.timeoutClientMap[timeout]
|
||||
if ok {
|
||||
return client
|
||||
}
|
||||
|
||||
client = &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.Dial(network, "127.0.0.1:"+port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connutils.NewNoStat(conn), nil
|
||||
},
|
||||
MaxIdleConns: 128,
|
||||
MaxIdleConnsPerHost: 32,
|
||||
MaxConnsPerHost: 32,
|
||||
IdleConnTimeout: 2 * time.Minute,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSHandshakeTimeout: 0,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
this.timeoutClientMap[timeout] = client
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"golang.org/x/net/http2"
|
||||
"net"
|
||||
@@ -20,6 +21,8 @@ import (
|
||||
// SharedHTTPClientPool HTTP客户端池单例
|
||||
var SharedHTTPClientPool = NewHTTPClientPool()
|
||||
|
||||
const httpClientProxyProtocolTag = "@ProxyProtocol@"
|
||||
|
||||
// HTTPClientPool 客户端池
|
||||
type HTTPClientPool struct {
|
||||
clientsMap map[string]*HTTPClient // backend key => client
|
||||
@@ -54,6 +57,14 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
}
|
||||
|
||||
var key = origin.UniqueKey() + "@" + originAddr
|
||||
|
||||
// if we are under available ProxyProtocol, we add client ip to key to make every client unique
|
||||
var isProxyProtocol = false
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn {
|
||||
key += httpClientProxyProtocolTag + req.requestRemoteAddr(true)
|
||||
isProxyProtocol = true
|
||||
}
|
||||
|
||||
var isLnRequest = origin.Id == 0
|
||||
|
||||
this.locker.RLock()
|
||||
@@ -102,8 +113,9 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
idleConns = numberCPU * 16
|
||||
}
|
||||
|
||||
// 可以判断为Ln节点请求
|
||||
if isLnRequest {
|
||||
if isProxyProtocol { // ProxyProtocol无需保持太多空闲连接
|
||||
idleConns = 3
|
||||
} else if isLnRequest { // 可以判断为Ln节点请求
|
||||
maxConnections *= 8
|
||||
idleConns *= 8
|
||||
idleTimeout *= 4
|
||||
@@ -146,7 +158,7 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
return NewOriginConn(conn), nil
|
||||
},
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: idleConns,
|
||||
@@ -195,22 +207,45 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
// 清理不使用的Client
|
||||
func (this *HTTPClientPool) cleanClients() {
|
||||
for range this.cleanTicker.C {
|
||||
var nowTime = time.Now().Unix()
|
||||
var nowTime = fasttime.Now().Unix()
|
||||
|
||||
this.locker.Lock()
|
||||
var expiredKeys = []string{}
|
||||
var expiredClients = []*HTTPClient{}
|
||||
|
||||
// lookup expired clients
|
||||
this.locker.RLock()
|
||||
for k, client := range this.clientsMap {
|
||||
if client.AccessTime() < nowTime+86400 { // 超过 N 秒没有调用就关闭
|
||||
if client.AccessTime() < nowTime-86400 ||
|
||||
(strings.Contains(k, httpClientProxyProtocolTag) && client.AccessTime() < nowTime-3600) { // 超过 N 秒没有调用就关闭
|
||||
expiredKeys = append(expiredKeys, k)
|
||||
expiredClients = append(expiredClients, client)
|
||||
}
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
// remove expired keys
|
||||
if len(expiredKeys) > 0 {
|
||||
this.locker.Lock()
|
||||
for _, k := range expiredKeys {
|
||||
delete(this.clientsMap, k)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// close expired clients
|
||||
if len(expiredClients) > 0 {
|
||||
for _, client := range expiredClients {
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// 支持PROXY Protocol
|
||||
func (this *HTTPClientPool) handlePROXYProtocol(conn net.Conn, req *HTTPRequest, proxyProtocol *serverconfigs.ProxyProtocolConfig) error {
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
if proxyProtocol != nil &&
|
||||
proxyProtocol.IsOn &&
|
||||
(proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
var remoteAddr = req.requestRemoteAddr(true)
|
||||
var transportProtocol = proxyproto.TCPv4
|
||||
if strings.Contains(remoteAddr, ":") {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -16,7 +17,7 @@ func TestHTTPClientPool_Client(t *testing.T) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init(nil)
|
||||
err := origin.Init(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -43,7 +44,7 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init(nil)
|
||||
err := origin.Init(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func BenchmarkHTTPClientPool_Client(b *testing.B) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init(nil)
|
||||
err := origin.Init(context.Background())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -38,15 +38,16 @@ type HTTPRequest struct {
|
||||
requestId string
|
||||
|
||||
// 外部参数
|
||||
RawReq *http.Request
|
||||
RawWriter http.ResponseWriter
|
||||
ReqServer *serverconfigs.ServerConfig
|
||||
ReqHost string // 请求的Host
|
||||
ServerName string // 实际匹配到的Host
|
||||
ServerAddr string // 实际启动的服务器监听地址
|
||||
IsHTTP bool
|
||||
IsHTTPS bool
|
||||
IsHTTP3 bool
|
||||
RawReq *http.Request
|
||||
RawWriter http.ResponseWriter
|
||||
ReqServer *serverconfigs.ServerConfig
|
||||
ReqHost string // 请求的Host
|
||||
ServerName string // 实际匹配到的Host
|
||||
ServerAddr string // 实际启动的服务器监听地址
|
||||
IsHTTP bool
|
||||
IsHTTPS bool
|
||||
IsHTTP3 bool
|
||||
isHealthCheck bool
|
||||
|
||||
// 共享参数
|
||||
nodeConfig *nodeconfigs.NodeConfig
|
||||
@@ -168,10 +169,9 @@ func (this *HTTPRequest) Do() {
|
||||
}
|
||||
|
||||
// 处理健康检查
|
||||
var isHealthCheck = false
|
||||
var healthCheckKey = this.RawReq.Header.Get(serverconfigs.HealthCheckHeaderName)
|
||||
if len(healthCheckKey) > 0 {
|
||||
if this.doHealthCheck(healthCheckKey, &isHealthCheck) {
|
||||
if this.doHealthCheck(healthCheckKey, &this.isHealthCheck) {
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
@@ -198,14 +198,14 @@ func (this *HTTPRequest) Do() {
|
||||
}
|
||||
|
||||
// 流量限制
|
||||
if this.ReqServer.TrafficLimit != nil && this.ReqServer.TrafficLimit.IsOn && !this.ReqServer.TrafficLimit.IsEmpty() && this.ReqServer.TrafficLimitStatus != nil && this.ReqServer.TrafficLimitStatus.IsValid() {
|
||||
if this.ReqServer.TrafficLimitStatus != nil && this.ReqServer.TrafficLimitStatus.IsValid() {
|
||||
this.doTrafficLimit()
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
|
||||
// UAM
|
||||
if !isHealthCheck {
|
||||
if !this.isHealthCheck {
|
||||
if this.web.UAM != nil {
|
||||
if this.web.UAM.IsOn {
|
||||
if this.doUAM() {
|
||||
@@ -223,7 +223,7 @@ func (this *HTTPRequest) Do() {
|
||||
}
|
||||
|
||||
// CC
|
||||
if !isHealthCheck {
|
||||
if !this.isHealthCheck {
|
||||
if this.web.CC != nil {
|
||||
if this.web.CC.IsOn {
|
||||
if this.doCC() {
|
||||
@@ -388,7 +388,22 @@ func (this *HTTPRequest) doEnd() {
|
||||
|
||||
// 流量统计
|
||||
// TODO 增加是否开启开关
|
||||
if this.ReqServer != nil && this.ReqServer.Id > 0 {
|
||||
if this.ReqServer != nil && this.ReqServer.Id > 0 && !this.isHealthCheck /** 健康检查时不统计 **/ {
|
||||
var totalBytes int64 = 0
|
||||
|
||||
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if requestConn != nil {
|
||||
requestClientConn, ok := requestConn.(ClientConnInterface)
|
||||
if ok {
|
||||
// 这里读取的其实是上一个请求消耗的流量,不是当前请求消耗的流量,只不过单个请求的流量统计不需要特别精确,整体趋于一致即可
|
||||
totalBytes = requestClientConn.LastRequestBytes()
|
||||
}
|
||||
}
|
||||
|
||||
if totalBytes == 0 {
|
||||
totalBytes = this.writer.SentBodyBytes() + this.writer.SentHeaderBytes()
|
||||
}
|
||||
|
||||
var countCached int64 = 0
|
||||
var cachedBytes int64 = 0
|
||||
|
||||
@@ -397,14 +412,17 @@ func (this *HTTPRequest) doEnd() {
|
||||
|
||||
if this.isCached {
|
||||
countCached = 1
|
||||
cachedBytes = this.writer.SentBodyBytes() + this.writer.SentHeaderBytes()
|
||||
cachedBytes = totalBytes
|
||||
}
|
||||
if this.isAttack {
|
||||
countAttacks = 1
|
||||
attackBytes = this.CalculateSize()
|
||||
if attackBytes < totalBytes {
|
||||
attackBytes = totalBytes
|
||||
}
|
||||
}
|
||||
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes()+this.writer.SentHeaderBytes(), cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, totalBytes, cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
|
||||
// 指标
|
||||
if metrics.SharedManager.HasHTTPMetrics() {
|
||||
@@ -836,6 +854,24 @@ func (this *HTTPRequest) Format(source string) string {
|
||||
return this.requestHeadersString()
|
||||
case "serverName":
|
||||
return this.ServerName
|
||||
case "serverAddr":
|
||||
var nodeConfig = this.nodeConfig
|
||||
if nodeConfig != nil && nodeConfig.GlobalServerConfig != nil && nodeConfig.GlobalServerConfig.HTTPAll.EnableServerAddrVariable {
|
||||
if len(this.requestRemoteAddrs()) > 1 {
|
||||
return "" // hidden for security
|
||||
}
|
||||
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if requestConn != nil {
|
||||
conn, ok := requestConn.(net.Conn)
|
||||
if ok {
|
||||
host, _, _ := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if len(host) > 0 {
|
||||
return host
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
case "serverPort":
|
||||
return strconv.Itoa(this.requestServerPort())
|
||||
case "hostname":
|
||||
@@ -1135,10 +1171,32 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
|
||||
this.web.RemoteAddr != nil &&
|
||||
this.web.RemoteAddr.IsOn &&
|
||||
!this.web.RemoteAddr.IsEmpty() {
|
||||
var remoteAddr = this.Format(this.web.RemoteAddr.Value)
|
||||
if net.ParseIP(remoteAddr) != nil {
|
||||
this.remoteAddr = remoteAddr
|
||||
return remoteAddr
|
||||
if this.web.RemoteAddr.HasValues() { // multiple values
|
||||
for _, value := range this.web.RemoteAddr.Values() {
|
||||
var remoteAddr = this.Format(value)
|
||||
if len(remoteAddr) > 0 && net.ParseIP(remoteAddr) != nil {
|
||||
this.remoteAddr = remoteAddr
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
} else { // single value
|
||||
var remoteAddr = this.Format(this.web.RemoteAddr.Value)
|
||||
if len(remoteAddr) > 0 && net.ParseIP(remoteAddr) != nil {
|
||||
this.remoteAddr = remoteAddr
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是从Header中读取,则直接返回原始IP
|
||||
if this.web.RemoteAddr.Type == serverconfigs.HTTPRemoteAddrTypeRequestHeader {
|
||||
var remoteAddr = this.RawReq.RemoteAddr
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err == nil {
|
||||
this.remoteAddr = host
|
||||
return host
|
||||
} else {
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1204,7 +1262,7 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
|
||||
var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For")
|
||||
if len(forwardedFor) > 0 {
|
||||
commaIndex := strings.Index(forwardedFor, ",")
|
||||
if commaIndex > 0 {
|
||||
if commaIndex > 0 && !lists.ContainsString(result, forwardedFor[:commaIndex]) {
|
||||
result = append(result, forwardedFor[:commaIndex])
|
||||
}
|
||||
}
|
||||
@@ -1212,7 +1270,7 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
|
||||
// Real-IP
|
||||
{
|
||||
realIP, ok := this.RawReq.Header["X-Real-IP"]
|
||||
if ok && len(realIP) > 0 {
|
||||
if ok && len(realIP) > 0 && !lists.ContainsString(result, realIP[0]) {
|
||||
result = append(result, realIP[0])
|
||||
}
|
||||
}
|
||||
@@ -1220,7 +1278,7 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
|
||||
// Real-Ip
|
||||
{
|
||||
realIP, ok := this.RawReq.Header["X-Real-Ip"]
|
||||
if ok && len(realIP) > 0 {
|
||||
if ok && len(realIP) > 0 && !lists.ContainsString(result, realIP[0]) {
|
||||
result = append(result, realIP[0])
|
||||
}
|
||||
}
|
||||
@@ -1230,7 +1288,9 @@ func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
|
||||
var remoteAddr = this.RawReq.RemoteAddr
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err == nil {
|
||||
result = append(result, host)
|
||||
if !lists.ContainsString(result, host) {
|
||||
result = append(result, host)
|
||||
}
|
||||
} else {
|
||||
result = append(result, remoteAddr)
|
||||
}
|
||||
@@ -1547,10 +1607,10 @@ func (this *HTTPRequest) setForwardHeaders(header http.Header) {
|
||||
this.RawReq.Header.Set("Connection", "keep-alive")
|
||||
}
|
||||
|
||||
remoteAddr := this.RawReq.RemoteAddr
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
var rawRemoteAddr = this.RawReq.RemoteAddr
|
||||
host, _, err := net.SplitHostPort(rawRemoteAddr)
|
||||
if err == nil {
|
||||
remoteAddr = host
|
||||
rawRemoteAddr = host
|
||||
}
|
||||
|
||||
// x-real-ip
|
||||
@@ -1558,24 +1618,24 @@ func (this *HTTPRequest) setForwardHeaders(header http.Header) {
|
||||
_, ok1 := header["X-Real-IP"]
|
||||
_, ok2 := header["X-Real-Ip"]
|
||||
if !ok1 && !ok2 {
|
||||
header["X-Real-IP"] = []string{remoteAddr}
|
||||
header["X-Real-IP"] = []string{this.requestRemoteAddr(true)}
|
||||
}
|
||||
}
|
||||
|
||||
// X-Forwarded-For
|
||||
if this.reverseProxy != nil && this.reverseProxy.ShouldAddXForwardedForHeader() {
|
||||
forwardedFor, ok := header["X-Forwarded-For"]
|
||||
if ok {
|
||||
if ok && len(forwardedFor) > 0 { // already exists
|
||||
_, hasForwardHeader := this.RawReq.Header["X-Forwarded-For"]
|
||||
if hasForwardHeader {
|
||||
header["X-Forwarded-For"] = []string{strings.Join(forwardedFor, ", ") + ", " + remoteAddr}
|
||||
header["X-Forwarded-For"] = []string{strings.Join(forwardedFor, ", ") + ", " + rawRemoteAddr}
|
||||
}
|
||||
} else {
|
||||
var clientRemoteAddr = this.requestRemoteAddr(true)
|
||||
if len(clientRemoteAddr) > 0 && clientRemoteAddr != remoteAddr {
|
||||
header["X-Forwarded-For"] = []string{clientRemoteAddr + ", " + remoteAddr}
|
||||
if len(clientRemoteAddr) > 0 && clientRemoteAddr != rawRemoteAddr {
|
||||
header["X-Forwarded-For"] = []string{clientRemoteAddr + ", " + rawRemoteAddr}
|
||||
} else {
|
||||
header["X-Forwarded-For"] = []string{remoteAddr}
|
||||
header["X-Forwarded-For"] = []string{rawRemoteAddr}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// 是否强制Range回源
|
||||
if this.cacheRef.AlwaysForwardRangeRequest && len(this.RawReq.Header.Get("Range")) > 0 {
|
||||
this.cacheRef = nil
|
||||
cacheBypassDescription = "BYPASS, forward range"
|
||||
return
|
||||
}
|
||||
|
||||
// 是否正在Purge
|
||||
var isPurging = this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey
|
||||
if isPurging {
|
||||
|
||||
@@ -9,16 +9,20 @@ import (
|
||||
)
|
||||
|
||||
const httpStatusPageTemplate = `<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>${status} ${statusMessage}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<style>
|
||||
address { line-height: 1.8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>${status} ${statusMessage}</h1>
|
||||
<p>${message}</p>
|
||||
|
||||
<address>Connection: ${remoteAddr} (Client) -> ${serverAddr} (Server)</address>
|
||||
<address>Request ID: ${requestId}.</address>
|
||||
|
||||
</body>
|
||||
@@ -39,8 +43,6 @@ func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage s
|
||||
return types.String(statusCode)
|
||||
case "statusMessage":
|
||||
return http.StatusText(statusCode)
|
||||
case "requestId":
|
||||
return this.requestId
|
||||
case "message":
|
||||
var acceptLanguages = this.RawReq.Header.Get("Accept-Language")
|
||||
if len(acceptLanguages) > 0 {
|
||||
@@ -54,7 +56,7 @@ func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage s
|
||||
}
|
||||
return enMessage
|
||||
}
|
||||
return "${" + varName + "}"
|
||||
return this.Format("${" + varName + "}")
|
||||
})
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
@@ -107,7 +109,7 @@ func (this *HTTPRequest) write50x(err error, statusCode int, enMessage string, z
|
||||
}
|
||||
return "The site is unavailable now, cause: " + enMessage + "."
|
||||
}
|
||||
return "${" + varName + "}"
|
||||
return this.Format("${" + varName + "}")
|
||||
})
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
|
||||
@@ -111,7 +111,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
||||
|
||||
// 处理SCRIPT_FILENAME
|
||||
scriptPath := env.GetString("SCRIPT_FILENAME")
|
||||
if len(scriptPath) > 0 && (strings.Index(scriptPath, "/") < 0 && strings.Index(scriptPath, "\\") < 0) {
|
||||
if len(scriptPath) > 0 && !strings.Contains(scriptPath, "/") && !strings.Contains(scriptPath, "\\") {
|
||||
env["SCRIPT_FILENAME"] = env.GetString("DOCUMENT_ROOT") + Tea.DS + scriptPath
|
||||
}
|
||||
scriptFilename := filepath.Base(this.RawReq.URL.Path)
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
var fullURL = ""
|
||||
var fullURL string
|
||||
if u.BeforeHasQuery() {
|
||||
fullURL = this.URL()
|
||||
} else {
|
||||
@@ -55,7 +55,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
httpRedirect(this.writer, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
} else if u.MatchRegexp { // 正则匹配
|
||||
@@ -97,7 +97,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
httpRedirect(this.writer, this.RawReq, afterURL, status)
|
||||
return true
|
||||
} else { // 精准匹配
|
||||
if fullURL == u.RealBeforeURL() {
|
||||
@@ -120,7 +120,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
httpRedirect(this.writer, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -139,11 +139,6 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果跳转前后域名一致,则终止
|
||||
if u.DomainAfter == reqHost {
|
||||
return false
|
||||
}
|
||||
|
||||
var scheme = u.DomainAfterScheme
|
||||
if len(scheme) == 0 {
|
||||
scheme = this.requestScheme()
|
||||
@@ -155,6 +150,11 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果跳转前后域名一致,则终止
|
||||
if u.DomainAfter == reqHost {
|
||||
return false
|
||||
}
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
|
||||
// 参数
|
||||
@@ -163,7 +163,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
afterURL += this.uri[qIndex:]
|
||||
}
|
||||
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
httpRedirect(this.writer, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
} else if u.Type == serverconfigs.HTTPHostRedirectTypePort {
|
||||
@@ -194,7 +194,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
var containsPort = false
|
||||
var containsPort bool
|
||||
if u.PortsAll {
|
||||
containsPort = true
|
||||
} else {
|
||||
@@ -212,7 +212,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
httpRedirect(this.writer, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ func (this *HTTPRequest) checkLnRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) getLnOrigin(excludingNodeIds []int64) (originConfig *serverconfigs.OriginConfig, lnNodeId int64, hasMultipleNodes bool) {
|
||||
func (this *HTTPRequest) getLnOrigin(excludingNodeIds []int64, urlHash uint64) (originConfig *serverconfigs.OriginConfig, lnNodeId int64, hasMultipleNodes bool) {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -48,7 +48,8 @@ func (this *HTTPRequest) doMismatch() {
|
||||
}
|
||||
|
||||
// 是否正在访问IP
|
||||
if globalServerConfig.HTTPAll.NodeIPShowPage && net.ParseIP(this.ReqHost) != nil {
|
||||
if globalServerConfig.HTTPAll.NodeIPShowPage && utils.IsWildIP(this.ReqHost) {
|
||||
this.writer.statusCode = statusCode
|
||||
var contentHTML = this.Format(globalServerConfig.HTTPAll.NodeIPPageHTML)
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
|
||||
@@ -71,8 +72,9 @@ func (this *HTTPRequest) doMismatch() {
|
||||
}
|
||||
|
||||
// 处理当前连接
|
||||
if mismatchAction != nil && mismatchAction.Code == "page" {
|
||||
if mismatchAction != nil && mismatchAction.Code == serverconfigs.DomainMismatchActionPage {
|
||||
if mismatchAction.Options != nil {
|
||||
this.writer.statusCode = statusCode
|
||||
var contentHTML = this.Format(mismatchAction.Options.GetString("contentHTML"))
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultPageContentType = "text/html; charset=utf-8"
|
||||
|
||||
// 请求特殊页面
|
||||
func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
||||
if len(this.web.Pages) == 0 {
|
||||
@@ -58,6 +60,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
var realpath = path.Clean(page.URL)
|
||||
if !strings.HasPrefix(realpath, "/pages/") && !strings.HasPrefix(realpath, "pages/") { // only files under "/pages/" can be used
|
||||
var msg = "404 page not found: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
@@ -66,6 +69,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
fp, err := os.Open(file)
|
||||
if err != nil {
|
||||
var msg = "404 page not found: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
@@ -77,6 +81,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
var msg = "404 could not read page content: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
@@ -85,10 +90,12 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||
this.writer.Prepare(nil, stat.Size(), page.NewStatus, true)
|
||||
this.writer.WriteHeader(page.NewStatus)
|
||||
} else {
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
this.writer.Prepare(nil, stat.Size(), status, true)
|
||||
this.writer.WriteHeader(status)
|
||||
@@ -120,10 +127,12 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||
this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true)
|
||||
this.writer.WriteHeader(page.NewStatus)
|
||||
} else {
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
this.writer.Prepare(nil, int64(len(content)), status, true)
|
||||
this.writer.WriteHeader(status)
|
||||
|
||||
@@ -43,7 +43,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
|
||||
|
||||
var newURL = "https://" + host + this.RawReq.RequestURI
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
|
||||
httpRedirect(this.writer, this.RawReq, newURL, statusCode)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -21,15 +23,13 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
return
|
||||
}
|
||||
|
||||
var isLowVersionHTTP = this.RawReq.ProtoMajor < 1 /** 0.x **/ || (this.RawReq.ProtoMajor == 1 && this.RawReq.ProtoMinor == 0 /** 1.0 **/)
|
||||
|
||||
var retries = 3
|
||||
|
||||
var failedOriginIds []int64
|
||||
var failedLnNodeIds []int64
|
||||
|
||||
for i := 0; i < retries; i++ {
|
||||
originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1, isLowVersionHTTP)
|
||||
originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1)
|
||||
if !shouldRetry {
|
||||
break
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
}
|
||||
|
||||
// 请求源站
|
||||
func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool, isLowVersionHTTP bool) (originId int64, lnNodeId int64, shouldRetry bool) {
|
||||
func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool) (originId int64, lnNodeId int64, shouldRetry bool) {
|
||||
// 对URL的处理
|
||||
var stripPrefix = this.reverseProxy.StripPrefix
|
||||
var requestURI = this.reverseProxy.RequestURI
|
||||
@@ -67,7 +67,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
// 二级节点
|
||||
var hasMultipleLnNodes = false
|
||||
if this.cacheRef != nil || (this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && this.nodeConfig.GlobalServerConfig.HTTPAll.ForceLnRequest) {
|
||||
origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds)
|
||||
origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds, fnv.HashString(this.URL()))
|
||||
if origin != nil {
|
||||
// 强制变更原来访问的域名
|
||||
requestHost = this.ReqHost
|
||||
@@ -258,6 +258,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var respBodyIsClosed bool
|
||||
var requestErr error
|
||||
var requestErrCode string
|
||||
if isHTTPOrigin { // 普通HTTP(S)源站
|
||||
@@ -296,9 +297,19 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.writeCode(http.StatusBadGateway, "The type of origin site has not been supported", "设置的源站类型尚未支持")
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer func() {
|
||||
if !respBodyIsClosed {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if requestErr != nil {
|
||||
// 客户端取消请求,则不提示
|
||||
httpErr, ok := requestErr.(*url.Error)
|
||||
var httpErr *url.Error
|
||||
ok := errors.As(requestErr, &httpErr)
|
||||
if !ok {
|
||||
if isHTTPOrigin {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
@@ -312,7 +323,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
}
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+requestErr.Error())
|
||||
} else if httpErr.Err != context.Canceled {
|
||||
} else if !errors.Is(httpErr, context.Canceled) {
|
||||
if isHTTPOrigin {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
@@ -324,10 +335,6 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
shouldRetry = true
|
||||
this.uri = oldURI // 恢复备份
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
if httpErr.Err != io.EOF {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
|
||||
}
|
||||
@@ -349,7 +356,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
// 是否为客户端方面的错误
|
||||
var isClientError = false
|
||||
if ok {
|
||||
if httpErr.Err == context.Canceled {
|
||||
if errors.Is(httpErr, context.Canceled) {
|
||||
// 如果是服务器端主动关闭,则无需提示
|
||||
if this.isConnClosed() {
|
||||
this.disableLog = true
|
||||
@@ -366,22 +373,38 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
}
|
||||
}
|
||||
if resp != nil && resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 是否为1.1以下
|
||||
if isLowVersionHTTP && resp.ContentLength < 0 {
|
||||
this.writer.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = this.writer.WriteString("The content does not support " + this.RawReq.Proto + " request.")
|
||||
// 50x
|
||||
if resp != nil &&
|
||||
resp.StatusCode >= 500 &&
|
||||
resp.StatusCode < 510 &&
|
||||
this.reverseProxy.Retry50X &&
|
||||
(originId > 0 || (lnNodeId > 0 && hasMultipleLnNodes)) &&
|
||||
!isLastRetry {
|
||||
if resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
shouldRetry = true
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试从缓存中恢复
|
||||
if resp != nil &&
|
||||
resp.StatusCode >= 500 && // support 50X only
|
||||
resp.StatusCode < 510 &&
|
||||
this.cacheCanTryStale &&
|
||||
this.web.Cache.Stale != nil &&
|
||||
this.web.Cache.Stale.IsOn &&
|
||||
(len(this.web.Cache.Stale.Status) == 0 || lists.ContainsInt(this.web.Cache.Stale.Status, resp.StatusCode)) {
|
||||
var ok = this.doCacheRead(true)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 记录相关数据
|
||||
this.originStatus = int32(resp.StatusCode)
|
||||
|
||||
@@ -395,20 +418,12 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
// WAF对出站进行检查
|
||||
if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn {
|
||||
if this.doWAFResponse(resp) {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊页面
|
||||
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error())
|
||||
}
|
||||
if this.doPage(resp.StatusCode) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -499,6 +514,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
_, _ = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
utils.BytePool4k.Put(buf)
|
||||
_ = resp.Body.Close()
|
||||
respBodyIsClosed = true
|
||||
|
||||
this.writer.SetOk()
|
||||
return
|
||||
@@ -524,11 +540,33 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
if this.cacheRef != nil &&
|
||||
this.cacheRef.EnableReadingOriginAsync &&
|
||||
resp.ContentLength > 0 &&
|
||||
resp.ContentLength < (128<<20) { // TODO configure max content-length in cache policy OR CacheRef
|
||||
var requestIsCanceled = false
|
||||
for {
|
||||
n, readErr := resp.Body.Read(buf)
|
||||
|
||||
if n > 0 && !requestIsCanceled {
|
||||
_, err = this.writer.Write(buf[:n])
|
||||
if err != nil {
|
||||
requestIsCanceled = true
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
err = readErr
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
}
|
||||
}
|
||||
pool.Put(buf)
|
||||
|
||||
var closeErr = resp.Body.Close()
|
||||
respBodyIsClosed = true
|
||||
if closeErr != nil {
|
||||
if !this.canIgnore(closeErr) {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error: "+closeErr.Error())
|
||||
|
||||
@@ -31,10 +31,10 @@ func (this *HTTPRequest) doRewrite() (shouldShop bool) {
|
||||
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
|
||||
if this.rewriteRule.RedirectStatus > 0 {
|
||||
this.ProcessResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
|
||||
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
|
||||
httpRedirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
|
||||
} else {
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
|
||||
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
|
||||
httpRedirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -66,6 +66,19 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
requestPath = this.uri[:questionMarkIndex]
|
||||
}
|
||||
|
||||
// except hidden files
|
||||
if this.web.Root.ExceptHiddenFiles &&
|
||||
(strings.Contains(requestPath, "/.") || strings.Contains(requestPath, "\\.")) {
|
||||
this.write404()
|
||||
return true
|
||||
}
|
||||
|
||||
// except and only files
|
||||
if !this.web.Root.MatchURL(this.URL()) {
|
||||
this.write404()
|
||||
return true
|
||||
}
|
||||
|
||||
// 去掉其中的奇怪的路径
|
||||
requestPath = strings.Replace(requestPath, "..\\", "", -1)
|
||||
|
||||
@@ -95,7 +108,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
}
|
||||
|
||||
var filename = strings.Replace(requestPath, "/", Tea.DS, -1)
|
||||
var filePath = ""
|
||||
var filePath string
|
||||
if len(filename) > 0 && filename[0:1] == Tea.DS {
|
||||
filePath = rootDir + filename
|
||||
} else {
|
||||
|
||||
@@ -8,15 +8,17 @@ import (
|
||||
|
||||
// 流量限制
|
||||
func (this *HTTPRequest) doTrafficLimit() {
|
||||
var config = this.ReqServer.TrafficLimit
|
||||
|
||||
this.tags = append(this.tags, "bandwidth")
|
||||
this.tags = append(this.tags, "trafficLimit")
|
||||
|
||||
var statusCode = 509
|
||||
this.writer.statusCode = statusCode
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.WriteHeader(statusCode)
|
||||
if len(config.NoticePageBody) != 0 {
|
||||
|
||||
var config = this.ReqServer.TrafficLimit
|
||||
if config != nil && len(config.NoticePageBody) != 0 {
|
||||
_, _ = this.writer.WriteString(this.Format(config.NoticePageBody))
|
||||
} else {
|
||||
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultTrafficLimitNoticePageBody))
|
||||
|
||||
@@ -174,11 +174,19 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
if !regionConfig.IsAllowedCountry(result.CountryId(), result.ProvinceId()) {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
|
||||
var promptHTML string
|
||||
if len(regionConfig.CountryHTML) > 0 {
|
||||
promptHTML = regionConfig.CountryHTML
|
||||
} else if this.ReqServer != nil && this.ReqServer.HTTPFirewallPolicy != nil && len(this.ReqServer.HTTPFirewallPolicy.DenyCountryHTML) > 0 {
|
||||
promptHTML = this.ReqServer.HTTPFirewallPolicy.DenyCountryHTML
|
||||
}
|
||||
|
||||
if len(promptHTML) > 0 {
|
||||
var formattedHTML = this.Format(promptHTML)
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(regionConfig.CountryHTML)))
|
||||
this.writer.Header().Set("Content-Length", types.String(len(formattedHTML)))
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.writer.Write([]byte(regionConfig.CountryHTML))
|
||||
_, _ = this.writer.Write([]byte(formattedHTML))
|
||||
} else {
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
}
|
||||
@@ -202,11 +210,19 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
if !regionConfig.IsAllowedProvince(result.CountryId(), result.ProvinceId()) {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
|
||||
var promptHTML string
|
||||
if len(regionConfig.ProvinceHTML) > 0 {
|
||||
promptHTML = regionConfig.ProvinceHTML
|
||||
} else if this.ReqServer != nil && this.ReqServer.HTTPFirewallPolicy != nil && len(this.ReqServer.HTTPFirewallPolicy.DenyProvinceHTML) > 0 {
|
||||
promptHTML = this.ReqServer.HTTPFirewallPolicy.DenyProvinceHTML
|
||||
}
|
||||
|
||||
if len(promptHTML) > 0 {
|
||||
var formattedHTML = this.Format(promptHTML)
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(regionConfig.ProvinceHTML)))
|
||||
this.writer.Header().Set("Content-Length", types.String(len(formattedHTML)))
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.writer.Write([]byte(regionConfig.ProvinceHTML))
|
||||
_, _ = this.writer.Write([]byte(formattedHTML))
|
||||
} else {
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
}
|
||||
@@ -441,6 +457,14 @@ func (this *HTTPRequest) WAFFingerprint() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) WAFMaxRequestSize() int64 {
|
||||
var maxRequestSize = firewallconfigs.DefaultMaxRequestBodySize
|
||||
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.MaxRequestBodySize > 0 {
|
||||
maxRequestSize = this.ReqServer.HTTPFirewallPolicy.MaxRequestBodySize
|
||||
}
|
||||
return maxRequestSize
|
||||
}
|
||||
|
||||
// DisableAccessLog 在当前请求中不使用访问日志
|
||||
func (this *HTTPRequest) DisableAccessLog() {
|
||||
this.disableLog = true
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/readers"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/writers"
|
||||
_ "github.com/biessek/golang-ico"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
@@ -24,7 +26,6 @@ import (
|
||||
_ "golang.org/x/image/webp"
|
||||
"image"
|
||||
"image/gif"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
@@ -39,6 +40,7 @@ import (
|
||||
|
||||
var webpMaxBufferSize int64 = 1_000_000_000
|
||||
var webpTotalBufferSize int64 = 0
|
||||
var webpIgnoreURLSet = setutils.NewFixedSet(131072)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
@@ -47,7 +49,7 @@ func init() {
|
||||
|
||||
var systemMemory = utils.SystemMemoryGB() / 8
|
||||
if systemMemory > 0 {
|
||||
webpMaxBufferSize = int64(systemMemory) * 1024 * 1024 * 1024
|
||||
webpMaxBufferSize = int64(systemMemory) << 30
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,10 +364,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
||||
// 写入Header
|
||||
var headerBuf = utils.SharedBufferPool.Get()
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" ||
|
||||
(this.isPartial && k == "Content-Range") {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
for _, v1 := range v {
|
||||
@@ -410,7 +409,9 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
||||
var filterReader = readers.NewFilterReaderCloser(resp.Body)
|
||||
this.cacheIsFinished = true
|
||||
var hasError = false
|
||||
filterReader.Add(func(p []byte, err error) error {
|
||||
filterReader.Add(func(p []byte, readErr error) error {
|
||||
// 这里不用处理readErr,因为只要把成功读取的部分写入缓存即可
|
||||
|
||||
if hasError {
|
||||
return nil
|
||||
}
|
||||
@@ -548,6 +549,11 @@ func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) {
|
||||
this.req.web.WebP.IsOn &&
|
||||
this.req.web.WebP.MatchResponse(contentType, size, filepath.Ext(this.req.Path()), this.req.Format) &&
|
||||
this.req.web.WebP.MatchAccept(this.req.requestHeader("Accept")) {
|
||||
// 检查是否已经因为尺寸过大而忽略
|
||||
if webpIgnoreURLSet.Has(this.req.URL()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已经是WebP不再重复处理
|
||||
// TODO 考虑是否需要很严格的匹配
|
||||
if strings.Contains(contentType, "image/webp") {
|
||||
@@ -693,10 +699,7 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
||||
// 写入Header
|
||||
var headerBuffer = utils.SharedBufferPool.Get()
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" ||
|
||||
(this.isPartial && k == "Content-Range") {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
for _, v1 := range v {
|
||||
@@ -735,7 +738,6 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
||||
}
|
||||
|
||||
// compression writer
|
||||
var err error = nil
|
||||
compressionWriter, err := compressions.NewWriter(this.writer, compressionType, int(this.compressionConfig.Level))
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_WRITER", err.Error())
|
||||
@@ -792,12 +794,10 @@ func (this *HTTPWriter) AddHeaders(header http.Header) {
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "ETag":
|
||||
case "Accept-CH", "ETag", "Content-MD5", "IM", "P3P", "WWW-Authenticate", "X-Request-ID":
|
||||
newHeaders[key] = value
|
||||
default:
|
||||
for _, v := range value {
|
||||
newHeaders.Add(key, v)
|
||||
}
|
||||
newHeaders[http.CanonicalHeaderKey(key)] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -869,7 +869,7 @@ func (this *HTTPWriter) SendFile(status int, path string) (int64, error) {
|
||||
|
||||
fp, err := os.OpenFile(path, os.O_RDONLY, 0444)
|
||||
if err != nil {
|
||||
return 0, errors.New("open file '" + path + "' failed: " + err.Error())
|
||||
return 0, fmt.Errorf("open file '%s' failed: %w", path, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
@@ -913,7 +913,7 @@ func (this *HTTPWriter) SendResp(resp *http.Response) (int64, error) {
|
||||
|
||||
// Redirect 跳转
|
||||
func (this *HTTPWriter) Redirect(status int, url string) {
|
||||
http.Redirect(this.rawWriter, this.req.RawReq, url, status)
|
||||
httpRedirect(this, this.req.RawReq, url, status)
|
||||
this.isFinished = true
|
||||
}
|
||||
|
||||
@@ -989,7 +989,7 @@ func (this *HTTPWriter) DelayRead() bool {
|
||||
|
||||
// 计算stale时长
|
||||
func (this *HTTPWriter) calculateStaleLife() int {
|
||||
var staleLife = 600 // TODO 可以在缓存策略里设置此时间
|
||||
var staleLife = caches.DefaultStaleCacheSeconds
|
||||
var staleConfig = this.req.web.Cache.Stale
|
||||
if staleConfig != nil && staleConfig.IsOn {
|
||||
// 从Header中读取stale-if-error
|
||||
@@ -1039,9 +1039,7 @@ func (this *HTTPWriter) finishWebP() {
|
||||
if webpCacheWriter != nil {
|
||||
// 写入Header
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1063,7 +1061,9 @@ func (this *HTTPWriter) finishWebP() {
|
||||
if webpCacheWriter != nil {
|
||||
var teeWriter = writers.NewTeeWriterCloser(this.writer, webpCacheWriter)
|
||||
teeWriter.OnFail(func(err error) {
|
||||
_ = webpCacheWriter.Discard()
|
||||
if webpCacheWriter != nil {
|
||||
_ = webpCacheWriter.Discard()
|
||||
}
|
||||
webpCacheWriter = nil
|
||||
})
|
||||
this.writer = teeWriter
|
||||
@@ -1079,12 +1079,24 @@ func (this *HTTPWriter) finishWebP() {
|
||||
var err error
|
||||
if isGif {
|
||||
gifImage, err = gif.DecodeAll(reader)
|
||||
if gifImage != nil && (gifImage.Config.Width > gowebp.WebPMaxDimension || gifImage.Config.Height > gowebp.WebPMaxDimension) {
|
||||
webpIgnoreURLSet.Push(this.req.URL())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
imageData, _, err = image.Decode(reader)
|
||||
if imageData != nil {
|
||||
var bound = imageData.Bounds()
|
||||
if bound.Max.X > gowebp.WebPMaxDimension || bound.Max.Y > gowebp.WebPMaxDimension {
|
||||
webpIgnoreURLSet.Push(this.req.URL())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// 发生了错误终止处理
|
||||
webpIgnoreURLSet.Push(this.req.URL())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1260,10 +1272,7 @@ func (this *HTTPWriter) finishRequest() {
|
||||
// 计算Header长度
|
||||
func (this *HTTPWriter) calculateHeaderLength() (result int) {
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" ||
|
||||
(this.isPartial && k == "Content-Range") {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
for _, v1 := range v {
|
||||
@@ -1272,3 +1281,12 @@ func (this *HTTPWriter) calculateHeaderLength() (result int) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPWriter) shouldIgnoreHeader(name string) bool {
|
||||
switch name {
|
||||
case "Set-Cookie", "Strict-Transport-Security", "Alt-Svc", "Upgrade", "X-Cache":
|
||||
return true
|
||||
default:
|
||||
return (this.isPartial && name == "Content-Range")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
)
|
||||
@@ -38,7 +39,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
||||
// 指纹信息
|
||||
var fingerprint = this.calculateFingerprint(clientInfo)
|
||||
if len(fingerprint) > 0 {
|
||||
if len(fingerprint) > 0 && clientInfo.Conn != nil {
|
||||
clientConn, ok := clientInfo.Conn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetFingerprint(fingerprint)
|
||||
@@ -61,7 +62,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
||||
// 指纹信息
|
||||
var fingerprint = this.calculateFingerprint(clientInfo)
|
||||
if len(fingerprint) > 0 {
|
||||
if len(fingerprint) > 0 && clientInfo.Conn != nil {
|
||||
clientConn, ok := clientInfo.Conn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetFingerprint(fingerprint)
|
||||
@@ -179,10 +180,10 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
|
||||
|
||||
if globalServerConfig != nil &&
|
||||
len(globalServerConfig.HTTPAll.DefaultDomain) > 0 &&
|
||||
(!matchDomainStrictly || configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) || (globalServerConfig.HTTPAll.AllowNodeIP && net.ParseIP(name) != nil)) {
|
||||
(!matchDomainStrictly || configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) || (globalServerConfig.HTTPAll.AllowNodeIP && utils.IsWildIP(name))) {
|
||||
if globalServerConfig.HTTPAll.AllowNodeIP &&
|
||||
globalServerConfig.HTTPAll.NodeIPShowPage &&
|
||||
net.ParseIP(name) != nil {
|
||||
utils.IsWildIP(name) {
|
||||
return
|
||||
} else {
|
||||
var defaultDomain = globalServerConfig.HTTPAll.DefaultDomain
|
||||
@@ -193,7 +194,7 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
|
||||
}
|
||||
}
|
||||
|
||||
if matchDomainStrictly && !configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!globalServerConfig.HTTPAll.AllowNodeIP || net.ParseIP(name) == nil) {
|
||||
if matchDomainStrictly && !configutils.MatchDomains(globalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!globalServerConfig.HTTPAll.AllowNodeIP || !utils.IsWildIP(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -235,7 +236,7 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
|
||||
// 从Hello信息中获取服务名称
|
||||
func (this *BaseListener) helloServerName(clientInfo *tls.ClientHelloInfo) string {
|
||||
var serverName = clientInfo.ServerName
|
||||
if len(serverName) == 0 {
|
||||
if len(serverName) == 0 && clientInfo.Conn != nil {
|
||||
var localAddr = clientInfo.Conn.LocalAddr()
|
||||
if localAddr != nil {
|
||||
tcpAddr, ok := localAddr.(*net.TCPAddr)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -23,7 +24,7 @@ func TestBaseListener_FindServer(t *testing.T) {
|
||||
{Name: types.String(i) + ".hello.com"},
|
||||
},
|
||||
}
|
||||
_ = server.Init(nil)
|
||||
_ = server.Init(context.Background())
|
||||
listener.Group.Add(server)
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,12 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
|
||||
return
|
||||
}
|
||||
clientConn.SetUserId(server.UserId)
|
||||
|
||||
var userPlanId int64
|
||||
if server.UserPlan != nil && server.UserPlan.Id > 0 {
|
||||
userPlanId = server.UserPlan.Id
|
||||
}
|
||||
clientConn.SetUserPlanId(userPlanId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,7 +216,7 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
|
||||
// 检查host是否为IP
|
||||
func (this *HTTPListener) isIP(host string) bool {
|
||||
// IPv6
|
||||
if strings.Index(host, "[") > -1 {
|
||||
if strings.Contains(host, "[") {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -141,7 +141,7 @@ func (this *ListenerManager) Start(nodeConfig *nodeconfigs.NodeConfig) error {
|
||||
var port = addr[portIndex+1:]
|
||||
var processName = this.findProcessNameWithPort(group.IsUDP(), port)
|
||||
if len(processName) > 0 {
|
||||
err = errors.New(err.Error() + " (the process using port: '" + processName + "')")
|
||||
err = fmt.Errorf("%w (the process using port: '%s')", err, processName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,12 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
return nil
|
||||
}
|
||||
clientConn.SetUserId(server.UserId)
|
||||
|
||||
var userPlanId int64
|
||||
if server.UserPlan != nil && server.UserPlan.Id > 0 {
|
||||
userPlanId = server.UserPlan.Id
|
||||
}
|
||||
clientConn.SetUserPlanId(userPlanId)
|
||||
} else {
|
||||
tlsConn, ok := conn.(*tls.Conn)
|
||||
if ok {
|
||||
@@ -92,6 +98,12 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
return nil
|
||||
}
|
||||
clientConn.SetUserId(server.UserId)
|
||||
|
||||
var userPlanId int64
|
||||
if server.UserPlan != nil && server.UserPlan.Id > 0 {
|
||||
userPlanId = server.UserPlan.Id
|
||||
}
|
||||
clientConn.SetUserPlanId(userPlanId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,7 +404,11 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener
|
||||
stats.SharedTrafficStatManager.Add(server.UserId, server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
|
||||
// 带宽
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(server.UserId, server.Id, int64(n), int64(n))
|
||||
var userPlanId int64
|
||||
if server.UserPlan != nil && server.UserPlan.Id > 0 {
|
||||
userPlanId = server.UserPlan.Id
|
||||
}
|
||||
stats.SharedBandwidthStatManager.AddBandwidth(server.UserId, userPlanId, server.Id, int64(n), int64(n))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
_ "github.com/TeaOSLab/EdgeNode/internal/utils/agents" // 引入Agent管理器
|
||||
_ "github.com/TeaOSLab/EdgeNode/internal/utils/clock" // 触发时钟更新
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/jsonutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/andybalholm/brotli"
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -97,11 +96,11 @@ func (this *Node) Test() error {
|
||||
// 检查是否能连接API
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("test rpc failed: " + err.Error())
|
||||
return fmt.Errorf("test rpc failed: %w", err)
|
||||
}
|
||||
_, err = rpcClient.APINodeRPC.FindCurrentAPINodeVersion(rpcClient.Context(), &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
return errors.New("test rpc failed: " + err.Error())
|
||||
return fmt.Errorf("test rpc failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -143,9 +142,6 @@ func (this *Node) Start() {
|
||||
// 调整系统参数
|
||||
this.checkSystem()
|
||||
|
||||
// 检查硬盘
|
||||
this.checkDisk()
|
||||
|
||||
// 启动事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
@@ -199,7 +195,7 @@ func (this *Node) Start() {
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
teaconst.NodeIdString = types.String(teaconst.NodeId)
|
||||
err, serverErrors := nodeConfig.Init(nil)
|
||||
err, serverErrors := nodeConfig.Init(context.Background())
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "init node config failed: "+err.Error())
|
||||
return
|
||||
@@ -317,17 +313,17 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
apiConfigFile := Tea.ConfigFile("api.yaml")
|
||||
// 检查api_node.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile(configs.ConfigFileName)
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
clusterErr := this.checkClusterConfig()
|
||||
if clusterErr != nil {
|
||||
if os.IsNotExist(clusterErr) {
|
||||
return errors.New("can not find config file 'configs/api.yaml'")
|
||||
return fmt.Errorf("can not find config file 'configs/%s'", configs.ConfigFileName)
|
||||
}
|
||||
return errors.New("check cluster config failed: " + clusterErr.Error())
|
||||
return fmt.Errorf("check cluster config failed: %w", clusterErr)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
@@ -336,7 +332,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("create rpc client failed: " + err.Error())
|
||||
return fmt.Errorf("create rpc client failed: %w", err)
|
||||
}
|
||||
|
||||
// 获取同步任务
|
||||
@@ -348,7 +344,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
UseDataMap: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("read config from rpc failed: " + err.Error())
|
||||
return fmt.Errorf("read config from rpc failed: %w", err)
|
||||
}
|
||||
if !configResp.IsChanged {
|
||||
return nil
|
||||
@@ -376,7 +372,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
var nodeConfig = &nodeconfigs.NodeConfig{}
|
||||
err = json.Unmarshal(configJSON, nodeConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode config failed: " + err.Error())
|
||||
return fmt.Errorf("decode config failed: %w", err)
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
teaconst.NodeIdString = types.String(teaconst.NodeId)
|
||||
@@ -396,7 +392,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err, serverErrors := nodeConfig.Init(nil)
|
||||
err, serverErrors := nodeConfig.Init(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -527,21 +523,15 @@ func (this *Node) startSyncTimer() {
|
||||
|
||||
// 检查集群设置
|
||||
func (this *Node) checkClusterConfig() error {
|
||||
configFile := Tea.ConfigFile("cluster.yaml")
|
||||
data, err := os.ReadFile(configFile)
|
||||
config, err := configs.LoadClusterConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := &configs.ClusterConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: config.RPC,
|
||||
NodeId: config.ClusterId,
|
||||
Secret: config.Secret,
|
||||
RPCEndpoints: config.RPCEndpoints,
|
||||
RPCDisableUpdate: config.RPCDisableUpdate,
|
||||
NodeId: config.ClusterId,
|
||||
Secret: config.Secret,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -559,22 +549,17 @@ func (this *Node) checkClusterConfig() error {
|
||||
resp.Endpoints = []string{}
|
||||
}
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: resp.Endpoints,
|
||||
DisableUpdate: false,
|
||||
},
|
||||
NodeId: resp.UniqueId,
|
||||
Secret: resp.Secret,
|
||||
RPCEndpoints: resp.Endpoints,
|
||||
RPCDisableUpdate: false,
|
||||
NodeId: resp.UniqueId,
|
||||
Secret: resp.Secret,
|
||||
}
|
||||
remotelogs.Debug("NODE", "writing 'configs/api.yaml' ...")
|
||||
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
remotelogs.Debug("NODE", "writing 'configs/"+configs.ConfigFileName+"' ...")
|
||||
err = apiConfig.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Debug("NODE", "wrote 'configs/api.yaml' successfully")
|
||||
remotelogs.Debug("NODE", "wrote 'configs/"+configs.ConfigFileName+"' successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -823,6 +808,36 @@ func (this *Node) listenSock() error {
|
||||
_ = cmd.Reply(&gosock.Command{Params: maps.Map{
|
||||
"stats": m,
|
||||
}})
|
||||
case "cache.garbage":
|
||||
var shouldDelete = maps.NewMap(cmd.Params).GetBool("delete")
|
||||
|
||||
var count = 0
|
||||
var sampleFiles = []string{}
|
||||
err := caches.SharedManager.ScanGarbageCaches(func(path string) error {
|
||||
count++
|
||||
if len(sampleFiles) < 10 {
|
||||
sampleFiles = append(sampleFiles, path)
|
||||
}
|
||||
|
||||
if shouldDelete {
|
||||
_ = os.Remove(path) // .cache
|
||||
_ = os.Remove(caches.PartialRangesFilePath(path)) // @range.cache
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
_ = cmd.Reply(&gosock.Command{Params: maps.Map{
|
||||
"isOk": false,
|
||||
"error": err.Error(),
|
||||
}})
|
||||
} else {
|
||||
_ = cmd.Reply(&gosock.Command{Params: maps.Map{
|
||||
"isOk": true,
|
||||
"count": count,
|
||||
"sampleFiles": sampleFiles,
|
||||
}})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1039,7 +1054,7 @@ func (this *Node) reloadServer() {
|
||||
remotelogs.Debug("NODE", "reload "+types.String(countUpdatingServers)+" servers")
|
||||
}
|
||||
|
||||
err, serverErrors := newNodeConfig.Init(nil)
|
||||
err, serverErrors := newNodeConfig.Init(context.Background())
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "apply server config error: "+err.Error())
|
||||
return
|
||||
@@ -1112,21 +1127,6 @@ func (this *Node) checkSystem() {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查硬盘
|
||||
func (this *Node) checkDisk() {
|
||||
speedMB, isFast, err := fsutils.CheckDiskIsFast()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "check disk speed failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
teaconst.DiskIsFast = isFast
|
||||
if isFast {
|
||||
remotelogs.Println("NODE", "disk is fast, writing test speed: "+fmt.Sprintf("%.2fMB/s", speedMB))
|
||||
} else {
|
||||
remotelogs.Println("NODE", "disk is slow, writing test speed: "+fmt.Sprintf("%.2fMB/s", speedMB))
|
||||
}
|
||||
}
|
||||
|
||||
// 检查API节点地址
|
||||
func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddressConfig) {
|
||||
var addrs = []string{}
|
||||
@@ -1154,7 +1154,7 @@ func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddres
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
var oldEndpoints = config.RPC.Endpoints
|
||||
var oldEndpoints = config.RPCEndpoints
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
@@ -1168,9 +1168,9 @@ func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddres
|
||||
go func(v int64) {
|
||||
// 测试新的API节点地址
|
||||
if rpcClient.TestEndpoints(addrs) {
|
||||
config.RPC.Endpoints = addrs
|
||||
config.RPCEndpoints = addrs
|
||||
} else {
|
||||
config.RPC.Endpoints = oldEndpoints
|
||||
config.RPCEndpoints = oldEndpoints
|
||||
this.lastAPINodeAddrs = nil // 恢复为空,以便于下次更新重试
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build arm64
|
||||
// +build arm64
|
||||
|
||||
package nodes
|
||||
|
||||
// 处理异常
|
||||
func (this *Node) handlePanic() {
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ func (this *NodeStatusExecutor) update() {
|
||||
status.BuildVersionCode = utils.VersionToLong(teaconst.Version)
|
||||
status.OS = runtime.GOOS
|
||||
status.Arch = runtime.GOARCH
|
||||
exe, _ := os.Executable()
|
||||
status.ExePath = exe
|
||||
status.ExePath, _ = os.Executable()
|
||||
status.ConfigVersion = sharedNodeConfig.Version
|
||||
status.IsActive = true
|
||||
status.ConnectionCount = sharedListenerManager.TotalActiveConnections()
|
||||
@@ -208,6 +207,8 @@ func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
|
||||
|
||||
// 更新硬盘
|
||||
func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
||||
status.DiskWritingSpeedMB = int(fsutils.DiskSpeedMB)
|
||||
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE_STATUS", err.Error())
|
||||
@@ -263,7 +264,9 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
||||
}
|
||||
}
|
||||
status.DiskTotal = total
|
||||
status.DiskUsage = float64(totalUsage) / float64(total)
|
||||
if total > 0 {
|
||||
status.DiskUsage = float64(totalUsage) / float64(total)
|
||||
}
|
||||
status.DiskMaxUsage = maxUsage / 100
|
||||
|
||||
// 记录监控数据
|
||||
@@ -279,7 +282,7 @@ func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus)
|
||||
var result = []maps.Map{}
|
||||
var cachePaths = caches.SharedManager.FindAllCachePaths()
|
||||
for _, path := range cachePaths {
|
||||
stat, err := fsutils.Stat(path)
|
||||
stat, err := fsutils.StatDevice(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,19 +5,24 @@ package nodes
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -26,8 +31,8 @@ func (this *Node) loopTasks() error {
|
||||
var tr = trackers.Begin("CHECK_NODE_CONFIG_CHANGES")
|
||||
defer tr.End()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile("api.yaml")
|
||||
// 检查api_node.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile(configs.ConfigFileName)
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -35,7 +40,7 @@ func (this *Node) loopTasks() error {
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("create rpc client failed: " + err.Error())
|
||||
return fmt.Errorf("create rpc client failed: %w", err)
|
||||
}
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
|
||||
@@ -45,7 +50,7 @@ func (this *Node) loopTasks() error {
|
||||
if rpc.IsConnError(err) && !Tea.IsTesting() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("read node tasks failed: " + err.Error())
|
||||
return fmt.Errorf("read node tasks failed: %w", err)
|
||||
}
|
||||
for _, task := range tasksResp.NodeTasks {
|
||||
err := this.execTask(rpcClient, task)
|
||||
@@ -93,7 +98,12 @@ func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
|
||||
case "toaChanged":
|
||||
err = this.execTOAChangedTask()
|
||||
default:
|
||||
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
|
||||
// 特殊任务
|
||||
if strings.HasPrefix(task.Type, "ipListDeleted") { // 删除IP名单
|
||||
err = this.execDeleteIPList(task.Type)
|
||||
} else { // 未处理的任务
|
||||
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -147,7 +157,7 @@ func (this *Node) execNodeLevelChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
|
||||
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
|
||||
if err != nil {
|
||||
return errors.New("decode level info failed: " + err.Error())
|
||||
return fmt.Errorf("decode level info failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +184,7 @@ func (this *Node) execDDoSProtectionChangedTask(rpcClient *rpc.RPCClient) error
|
||||
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
|
||||
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode DDoS protection config failed: " + err.Error())
|
||||
return fmt.Errorf("decode DDoS protection config failed: %w", err)
|
||||
}
|
||||
|
||||
if ddosProtectionConfig != nil && sharedNodeConfig != nil {
|
||||
@@ -202,13 +212,13 @@ func (this *Node) execGlobalServerConfigChangedTask(rpcClient *rpc.RPCClient) er
|
||||
var globalServerConfig = serverconfigs.NewGlobalServerConfig()
|
||||
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode global server config failed: " + err.Error())
|
||||
return fmt.Errorf("decode global server config failed: %w", err)
|
||||
}
|
||||
|
||||
if globalServerConfig != nil {
|
||||
err = globalServerConfig.Init()
|
||||
if err != nil {
|
||||
return errors.New("validate global server config failed: " + err.Error())
|
||||
return fmt.Errorf("validate global server config failed: %w", err)
|
||||
}
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.GlobalServerConfig = globalServerConfig
|
||||
@@ -256,7 +266,7 @@ func (this *Node) execUpdatingServersTask(rpcClient *rpc.RPCClient) error {
|
||||
var serverConfigs = []*serverconfigs.ServerConfig{}
|
||||
err = json.Unmarshal(resp.ServersJSON, &serverConfigs)
|
||||
if err != nil {
|
||||
return errors.New("decode server configs failed: " + err.Error())
|
||||
return fmt.Errorf("decode server configs failed: %w", err)
|
||||
}
|
||||
|
||||
if resp.MaxId > this.lastUpdatingServerListId {
|
||||
@@ -284,6 +294,34 @@ func (this *Node) execUpdatingServersTask(rpcClient *rpc.RPCClient) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除IP名单
|
||||
func (this *Node) execDeleteIPList(taskType string) error {
|
||||
optionsString, ok := strings.CutPrefix(taskType, "ipListDeleted@")
|
||||
if !ok {
|
||||
return errors.New("invalid task type '" + taskType + "'")
|
||||
}
|
||||
var optionMap = maps.Map{}
|
||||
err := json.Unmarshal([]byte(optionsString), &optionMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode options failed: %w, options: %s", err, optionsString)
|
||||
}
|
||||
var listId = optionMap.GetInt64("listId")
|
||||
if listId <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 标记已被删除
|
||||
waf.AddDeletedIPList(listId)
|
||||
|
||||
var list = iplibrary.SharedIPListManager.FindList(listId)
|
||||
|
||||
if list != nil {
|
||||
list.SetDeleted()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 标记任务完成
|
||||
func (this *Node) finishTask(taskId int64, taskVersion int64, taskErr error) (success bool) {
|
||||
if taskId <= 0 {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"io"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -22,29 +18,3 @@ func TestNode_Test(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestNode_Proto_Buffer(t *testing.T) {
|
||||
buff := proto.NewBuffer([]byte{})
|
||||
for i := 0; i < 10; i++ {
|
||||
err := buff.EncodeMessage(&pb.NodeStreamMessage{
|
||||
RequestId: int64(i),
|
||||
Code: "msg" + strconv.Itoa(i),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 11; i++ {
|
||||
msg := &pb.NodeStreamMessage{}
|
||||
err := buff.DecodeMessage(msg)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
t.Log(msg.Code, msg.RequestId)
|
||||
}
|
||||
}
|
||||
|
||||
102
internal/nodes/origin_conn.go
Normal file
102
internal/nodes/origin_conn.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const originConnCloseDelaySeconds = 3
|
||||
|
||||
var closingOriginConnMap = map[*OriginConn]zero.Zero{}
|
||||
var closingOriginConnLocker = &sync.RWMutex{}
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
var ticker = time.NewTicker(originConnCloseDelaySeconds * time.Second)
|
||||
for range ticker.C {
|
||||
CleanOriginConnsTask()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func CleanOriginConnsTask() {
|
||||
var closingConns = []*OriginConn{}
|
||||
|
||||
closingOriginConnLocker.RLock()
|
||||
for conn := range closingOriginConnMap {
|
||||
if conn.IsExpired() {
|
||||
closingConns = append(closingConns, conn)
|
||||
}
|
||||
}
|
||||
closingOriginConnLocker.RUnlock()
|
||||
|
||||
if len(closingConns) > 0 {
|
||||
for _, conn := range closingConns {
|
||||
_ = conn.ForceClose()
|
||||
closingOriginConnLocker.Lock()
|
||||
delete(closingOriginConnMap, conn)
|
||||
closingOriginConnLocker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OriginConn connection with origin site
|
||||
type OriginConn struct {
|
||||
net.Conn
|
||||
|
||||
lastReadOk bool
|
||||
lastReadAt int64
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
// NewOriginConn create new origin connection
|
||||
func NewOriginConn(rawConn net.Conn) net.Conn {
|
||||
return &OriginConn{Conn: rawConn}
|
||||
}
|
||||
|
||||
// Read implement Read() for net.Conn interface
|
||||
func (this *OriginConn) Read(b []byte) (n int, err error) {
|
||||
n, err = this.Conn.Read(b)
|
||||
this.lastReadOk = err == nil
|
||||
if this.lastReadOk {
|
||||
this.lastReadAt = fasttime.Now().Unix()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close implement Close() for net.Conn interface
|
||||
func (this *OriginConn) Close() error {
|
||||
if this.lastReadOk && fasttime.Now().Unix()-this.lastReadAt <= originConnCloseDelaySeconds {
|
||||
closingOriginConnLocker.Lock()
|
||||
closingOriginConnMap[this] = zero.Zero{}
|
||||
closingOriginConnLocker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = true
|
||||
return this.Conn.Close()
|
||||
}
|
||||
|
||||
func (this *OriginConn) ForceClose() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = true
|
||||
return this.Conn.Close()
|
||||
}
|
||||
|
||||
func (this *OriginConn) IsExpired() bool {
|
||||
return fasttime.Now().Unix()-this.lastReadAt > originConnCloseDelaySeconds
|
||||
}
|
||||
@@ -53,7 +53,11 @@ func (this *OCSPUpdateTask) Start() {
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Warn("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Debug("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||
} else {
|
||||
remotelogs.Warn("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPC.DisableUpdate {
|
||||
if config.RPCDisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
if utils.EqualStrings(newEndpoints, config.RPC.Endpoints) {
|
||||
if utils.EqualStrings(newEndpoints, config.RPCEndpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPC.Endpoints = newEndpoints
|
||||
config.RPCEndpoints = newEndpoints
|
||||
|
||||
// 更新当前RPC
|
||||
if !hasCustomizedAPINodeAddrs {
|
||||
@@ -118,7 +118,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func BenchmarkRegexp_MatchString(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkRegexp_MatchString2(b *testing.B) {
|
||||
var r = regexp.MustCompile("(?i)(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick|onkeydown|onkeyup|onkeypress)(\\s|%09|%0A|(\\+|%20))*(=|%3D)")
|
||||
var r = regexp.MustCompile(`(?i)(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick|onkeydown|onkeyup|onkeypress)(\s|%09|%0A|(\+|%20))*(=|%3D)`)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -60,7 +60,6 @@ func Println(tag string, description string) {
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +77,6 @@ func Warn(tag string, description string) {
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -228,10 +229,10 @@ func (this *RPCClient) TestEndpoints(endpoints []string) bool {
|
||||
func (this *RPCClient) init() error {
|
||||
// 重新连接
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPC.Endpoints {
|
||||
for _, endpoint := range this.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return errors.New("parse endpoint failed: " + err.Error())
|
||||
return fmt.Errorf("parse endpoint failed: %w", err)
|
||||
}
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
|
||||
@@ -62,6 +62,7 @@ type BandwidthStat struct {
|
||||
CountRequests int64 `json:"countRequests"`
|
||||
CountCachedRequests int64 `json:"countCachedRequests"`
|
||||
CountAttackRequests int64 `json:"countAttackRequests"`
|
||||
UserPlanId int64 `json:"userPlanId"`
|
||||
}
|
||||
|
||||
// BandwidthStatManager 服务带宽统计
|
||||
@@ -132,9 +133,10 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
for key, stat := range this.m {
|
||||
if stat.Day < day || stat.TimeAt < currentTime {
|
||||
// 防止数据出现错误
|
||||
if stat.CachedBytes > stat.TotalBytes {
|
||||
if stat.CachedBytes > stat.TotalBytes || stat.CountCachedRequests == stat.CountRequests {
|
||||
stat.CachedBytes = stat.TotalBytes
|
||||
}
|
||||
|
||||
if stat.AttackBytes > stat.TotalBytes {
|
||||
stat.AttackBytes = stat.TotalBytes
|
||||
}
|
||||
@@ -152,6 +154,7 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
CountRequests: stat.CountRequests,
|
||||
CountCachedRequests: stat.CountCachedRequests,
|
||||
CountAttackRequests: stat.CountAttackRequests,
|
||||
UserPlanId: stat.UserPlanId,
|
||||
NodeRegionId: regionId,
|
||||
})
|
||||
delete(this.m, key)
|
||||
@@ -177,7 +180,7 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
}
|
||||
|
||||
// AddBandwidth 添加带宽数据
|
||||
func (this *BandwidthStatManager) AddBandwidth(userId int64, serverId int64, peekBytes int64, totalBytes int64) {
|
||||
func (this *BandwidthStatManager) AddBandwidth(userId int64, userPlanId int64, serverId int64, peekBytes int64, totalBytes int64) {
|
||||
if serverId <= 0 || (peekBytes == 0 && totalBytes == 0) {
|
||||
return
|
||||
}
|
||||
@@ -216,6 +219,7 @@ func (this *BandwidthStatManager) AddBandwidth(userId int64, serverId int64, pee
|
||||
Day: day,
|
||||
TimeAt: timeAt,
|
||||
UserId: userId,
|
||||
UserPlanId: userPlanId,
|
||||
ServerId: serverId,
|
||||
CurrentBytes: peekBytes,
|
||||
MaxBytes: peekBytes,
|
||||
|
||||
@@ -12,22 +12,22 @@ import (
|
||||
|
||||
func TestBandwidthStatManager_Add(t *testing.T) {
|
||||
var manager = stats.NewBandwidthStatManager()
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 0, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 0, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 0, 1, 10, 10)
|
||||
time.Sleep(1 * time.Second)
|
||||
manager.AddBandwidth(1, 1, 85, 85)
|
||||
manager.AddBandwidth(1, 0, 1, 85, 85)
|
||||
time.Sleep(1 * time.Second)
|
||||
manager.AddBandwidth(1, 1, 25, 25)
|
||||
manager.AddBandwidth(1, 1, 75, 75)
|
||||
manager.AddBandwidth(1, 0, 1, 25, 25)
|
||||
manager.AddBandwidth(1, 0, 1, 75, 75)
|
||||
manager.Inspect()
|
||||
}
|
||||
|
||||
func TestBandwidthStatManager_Loop(t *testing.T) {
|
||||
var manager = stats.NewBandwidthStatManager()
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 0, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 0, 1, 10, 10)
|
||||
manager.AddBandwidth(1, 0, 1, 10, 10)
|
||||
err := manager.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -40,7 +40,7 @@ func BenchmarkBandwidthStatManager_Add(b *testing.B) {
|
||||
var i int
|
||||
for pb.Next() {
|
||||
i++
|
||||
manager.AddBandwidth(1, int64(i%100), 10, 10)
|
||||
manager.AddBandwidth(1, 0, int64(i%100), 10, 10)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -68,6 +68,7 @@ func BenchmarkBandwidthStatManager_Slice(b *testing.B) {
|
||||
NodeRegionId: 1,
|
||||
})
|
||||
}
|
||||
_ = pbStats
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ func BenchmarkBandwidthStatManager_Slice2(b *testing.B) {
|
||||
var stat = &stats.BandwidthStat{}
|
||||
statsSlice = append(statsSlice, stat)
|
||||
}
|
||||
_ = statsSlice
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user