Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
2390a3ef61 | ||
|
|
ffda81715f | ||
|
|
f991700031 | ||
|
|
e1ba6a90ff | ||
|
|
354161037b | ||
|
|
00d28df3ee | ||
|
|
23abed0949 | ||
|
|
2acf01dcb7 | ||
|
|
470c6a8b0e | ||
|
|
efc2810d1d | ||
|
|
de2374577f | ||
|
|
2a1f949c13 | ||
|
|
959e274063 | ||
|
|
b6a2bd37b1 | ||
|
|
3e60c9913a | ||
|
|
fd7f3f4029 | ||
|
|
2705a5d444 | ||
|
|
556055cfcb | ||
|
|
67a0d06944 | ||
|
|
a16d8f1afa | ||
|
|
1bab7bfcba | ||
|
|
5928875623 | ||
|
|
734cf81ff0 | ||
|
|
de8c2e13f1 | ||
|
|
0742dc963d | ||
|
|
1fdce3ef7e | ||
|
|
2079b0ebee | ||
|
|
c706f2fdf1 | ||
|
|
bd3247668d | ||
|
|
73024fe38c | ||
|
|
db520858b3 | ||
|
|
84c8a351a9 | ||
|
|
c6c0823d30 | ||
|
|
1be64adb6a | ||
|
|
d0610a5001 | ||
|
|
a2f7511a46 | ||
|
|
6e8c886cd6 | ||
|
|
03f2130827 | ||
|
|
9fea0749a0 | ||
|
|
71e0d9ce07 | ||
|
|
24e69028f8 | ||
|
|
34521dbc5c | ||
|
|
2c59ae4a5b | ||
|
|
d0bd7bb88d | ||
|
|
47c24b4aa8 | ||
|
|
4eb58a3082 | ||
|
|
c10c7cc157 | ||
|
|
7fd8d7756b | ||
|
|
032c118f49 | ||
|
|
b6cab3919a | ||
|
|
8edd30bdd8 | ||
|
|
a8c8d80e3b | ||
|
|
c43b6b37ea | ||
|
|
ac2d57d2f1 | ||
|
|
83ac62cda3 | ||
|
|
c0909a2cd0 | ||
|
|
a73b9f2674 | ||
|
|
3e79b71afc | ||
|
|
d3caccbb55 | ||
|
|
f95bac8d38 | ||
|
|
41d2ab728b | ||
|
|
b319061e85 | ||
|
|
99b8686a49 | ||
|
|
fe8c5b505a | ||
|
|
f88d0982ed | ||
|
|
a9389d53e1 | ||
|
|
fc4b45fec7 | ||
|
|
9b22e6cf69 | ||
|
|
7bd7f7da45 | ||
|
|
b68e6517df | ||
|
|
bbae229d08 | ||
|
|
c73a6cbfe8 | ||
|
|
e869e8e4d6 | ||
|
|
bde4e8507f | ||
|
|
5ae25cffa0 | ||
|
|
44b721d0d3 | ||
|
|
a2d6b7e0a8 | ||
|
|
95d65481e3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
*_plus.go
|
||||
*_plus_test.go
|
||||
*-plus.sh
|
||||
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
|
||||
@@ -49,15 +49,16 @@ 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"/
|
||||
|
||||
# we support TOA on linux/amd64 only
|
||||
if [ "$OS" == "linux" -a "$ARCH" == "amd64" ]
|
||||
# we support TOA on linux only
|
||||
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
|
||||
then
|
||||
cp -R "$ROOT"/edge-toa "$DIST"
|
||||
mkdir "$DIST/edge-toa"
|
||||
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
|
||||
fi
|
||||
|
||||
echo "building ..."
|
||||
@@ -114,7 +115,10 @@ function build() {
|
||||
if [ ! -z $CC_PATH ]; then
|
||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
else
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
BUILD_TAG="plus,script"
|
||||
fi
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
|
||||
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: ""
|
||||
Binary file not shown.
9
build/test.sh
Executable file
9
build/test.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TAG=${1}
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
go test -v ../... -tags=${TAG}
|
||||
@@ -5,8 +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"
|
||||
@@ -16,17 +20,89 @@ import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"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]").
|
||||
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 {
|
||||
@@ -365,6 +441,29 @@ func main() {
|
||||
}
|
||||
fmt.Println(string(statsJSON))
|
||||
})
|
||||
app.On("disk", func() {
|
||||
var args = os.Args[2:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "speed":
|
||||
speedMB, isFast, err := fsutils.CheckDiskIsFast()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
} else {
|
||||
fmt.Printf("Speed: %.0fMB/s\n", speedMB)
|
||||
if isFast {
|
||||
fmt.Println("IsFast: true")
|
||||
} else {
|
||||
fmt.Println("IsFast: false")
|
||||
}
|
||||
}
|
||||
default:
|
||||
fmt.Println("Usage: edge-node disk [speed]")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Usage: edge-node disk [speed]")
|
||||
}
|
||||
})
|
||||
app.Run(func() {
|
||||
var node = nodes.NewNode()
|
||||
node.Start()
|
||||
|
||||
53
go.mod
53
go.mod
@@ -10,50 +10,73 @@ replace (
|
||||
|
||||
require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/andybalholm/brotli v1.0.4
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/aws/aws-sdk-go v1.44.279
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/fsnotify/fsnotify v1.5.1
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/nftables v0.1.0
|
||||
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470
|
||||
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-20220819053541-c235395277b5
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508
|
||||
github.com/klauspost/compress v1.16.5
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/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/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.8.0
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/text v0.9.0
|
||||
google.golang.org/grpc v1.45.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/sys v0.10.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/webp v1.1.1 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
||||
|
||||
247
go.sum
247
go.sum
@@ -1,100 +1,98 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
|
||||
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
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/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470 h1:TuRxvKRv9PxKVijWOkUnZm5TeanQqWGUJyPx9u6cra4=
|
||||
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g=
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
|
||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5 h1:SgKhrqRhWVpu0ZKxQM8MGjdhy7hlH3Xax8snwsZWSic=
|
||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
||||
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/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=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/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/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
|
||||
@@ -103,148 +101,165 @@ github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw
|
||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
|
||||
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
|
||||
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
|
||||
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0 h1:Jt4YOMLuaDfgb/KdVg0O1fYLpv5MDkYe/zV+Ri7gWRs=
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
|
||||
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
|
||||
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw=
|
||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.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 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-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=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -86,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 {
|
||||
@@ -125,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
|
||||
@@ -150,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)
|
||||
|
||||
@@ -190,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,
|
||||
@@ -202,6 +208,9 @@ func (this *AppCmd) runStart() {
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
@@ -215,7 +224,7 @@ func (this *AppCmd) runStop() {
|
||||
|
||||
// 从systemd中停止
|
||||
if runtime.GOOS == "linux" {
|
||||
systemctl, _ := exec.LookPath("systemctl")
|
||||
systemctl, _ := executils.LookPath("systemctl")
|
||||
if len(systemctl) > 0 {
|
||||
go func() {
|
||||
// 有可能会长时间执行,这里不阻塞进程
|
||||
@@ -276,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import "errors"
|
||||
// 常用的几个错误
|
||||
var (
|
||||
ErrNotFound = errors.New("cache not found")
|
||||
ErrFileIsWriting = errors.New("the file is writing")
|
||||
ErrFileIsWriting = errors.New("the cache file is updating")
|
||||
ErrInvalidRange = errors.New("invalid range")
|
||||
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)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -21,7 +20,6 @@ const CountFileDB = 20
|
||||
type FileList struct {
|
||||
dir string
|
||||
dbList [CountFileDB]*FileListDB
|
||||
total int64
|
||||
|
||||
onAdd func(item *Item)
|
||||
onRemove func(item *Item)
|
||||
@@ -76,12 +74,6 @@ func (this *FileList) Init() error {
|
||||
this.dbList[i] = db
|
||||
}
|
||||
|
||||
// 读取总数量
|
||||
this.total = 0
|
||||
for _, db := range this.dbList {
|
||||
this.total += db.total
|
||||
}
|
||||
|
||||
// 升级老版本数据库
|
||||
goman.New(func() {
|
||||
this.upgradeOldDB()
|
||||
@@ -102,13 +94,11 @@ func (this *FileList) Add(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := db.AddAsync(hash, item)
|
||||
err := db.AddSync(hash, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.total, 1)
|
||||
|
||||
// 这里不增加点击量,以减少对数据库的操作次数
|
||||
|
||||
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
||||
@@ -296,8 +286,6 @@ func (this *FileList) CleanAll() error {
|
||||
}
|
||||
}
|
||||
|
||||
atomic.StoreInt64(&this.total, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -332,7 +320,15 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
// Count 总数量
|
||||
// 常用的方法,所以避免直接查询数据库
|
||||
func (this *FileList) Count() (int64, error) {
|
||||
return atomic.LoadInt64(&this.total), nil
|
||||
var total int64
|
||||
for _, db := range this.dbList {
|
||||
count, err := db.Total()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += count
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
@@ -392,37 +388,19 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
// 从缓存中删除
|
||||
this.memoryCache.Delete(hash)
|
||||
|
||||
var row = db.selectByHashStmt.QueryRow(hash)
|
||||
if row.Err() != nil {
|
||||
if row.Err() == sql.ErrNoRows {
|
||||
return true, nil
|
||||
}
|
||||
return false, row.Err()
|
||||
}
|
||||
|
||||
var item = &Item{Type: ItemTypeFile}
|
||||
err = row.Scan(&item.Key, &item.HeaderSize, &item.BodySize, &item.MetaSize, &item.ExpiredAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = db.DeleteAsync(hash)
|
||||
err = db.DeleteSync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.total, -1)
|
||||
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
// when remove file item, no any extra information needed
|
||||
this.onRemove(nil)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
@@ -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"
|
||||
@@ -35,8 +36,6 @@ type FileListDB struct {
|
||||
itemsTableName string
|
||||
hitsTableName string
|
||||
|
||||
total int64
|
||||
|
||||
isClosed bool
|
||||
isReady bool
|
||||
|
||||
@@ -85,7 +84,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)
|
||||
@@ -110,7 +109,7 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
this.writeBatch = dbs.NewBatch(writeDB.RawDB(), 4)
|
||||
this.writeBatch = dbs.NewBatch(writeDB, 4)
|
||||
this.writeBatch.OnFail(func(err error) {
|
||||
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
|
||||
})
|
||||
@@ -127,7 +126,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())
|
||||
@@ -148,21 +147,9 @@ func (this *FileListDB) Init() error {
|
||||
// 创建
|
||||
var err = this.initTables(1)
|
||||
if err != nil {
|
||||
return errors.New("init tables failed: " + err.Error())
|
||||
return fmt.Errorf("init tables failed: %w", err)
|
||||
}
|
||||
|
||||
// 读取总数量
|
||||
row := this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||
if row.Err() != nil {
|
||||
return row.Err()
|
||||
}
|
||||
var total int64
|
||||
err = row.Scan(&total)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.total = total
|
||||
|
||||
// 常用语句
|
||||
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||
if err != nil {
|
||||
@@ -250,20 +237,15 @@ func (this *FileListDB) IsReady() bool {
|
||||
return this.isReady
|
||||
}
|
||||
|
||||
func (this *FileListDB) Total() int64 {
|
||||
return this.total
|
||||
}
|
||||
|
||||
func (this *FileListDB) AddAsync(hash string, item *Item) error {
|
||||
this.hashMap.Add(hash)
|
||||
|
||||
if item.StaleAt == 0 {
|
||||
item.StaleAt = item.ExpiredAt
|
||||
func (this *FileListDB) Total() (int64, error) {
|
||||
// 读取总数量
|
||||
var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||
if row.Err() != nil {
|
||||
return 0, row.Err()
|
||||
}
|
||||
|
||||
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||
return nil
|
||||
|
||||
var total int64
|
||||
err := row.Scan(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
@@ -281,13 +263,6 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteAsync(hash string) error {
|
||||
this.hashMap.Delete(hash)
|
||||
|
||||
this.writeBatch.Add(this.deleteByHashSQL, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteSync(hash string) error {
|
||||
this.hashMap.Delete(hash)
|
||||
|
||||
@@ -504,6 +479,10 @@ func (this *FileListDB) CleanAll() error {
|
||||
}
|
||||
|
||||
func (this *FileListDB) Close() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = true
|
||||
this.isReady = false
|
||||
|
||||
@@ -535,10 +514,6 @@ func (this *FileListDB) Close() error {
|
||||
_ = this.listOlderItemsStmt.Close()
|
||||
}
|
||||
|
||||
if this.writeBatch != nil {
|
||||
this.writeBatch.Close()
|
||||
}
|
||||
|
||||
var errStrings []string
|
||||
|
||||
if this.readDB != nil {
|
||||
@@ -565,7 +540,7 @@ 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)
|
||||
}
|
||||
|
||||
// 初始化
|
||||
@@ -687,12 +662,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
|
||||
|
||||
@@ -12,6 +12,11 @@ import (
|
||||
|
||||
func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
//err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
|
||||
if err != nil {
|
||||
@@ -22,10 +27,6 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
hashList, err := db.ListLFUItems(100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -35,26 +36,46 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
|
||||
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait transaction
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
@@ -69,11 +90,20 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
|
||||
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
|
||||
@@ -59,15 +59,16 @@ func TestFileListHashMap_BigInt(t *testing.T) {
|
||||
|
||||
func TestFileListHashMap_Load(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var m = caches.NewFileListHashMap()
|
||||
var before = time.Now()
|
||||
var db = list.GetDB("abc")
|
||||
|
||||
@@ -5,6 +5,7 @@ package caches_test
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -17,6 +18,11 @@ import (
|
||||
|
||||
func TestFileList_Init(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)
|
||||
@@ -29,6 +35,11 @@ func TestFileList_Init(t *testing.T) {
|
||||
|
||||
func TestFileList_Add(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -59,16 +70,21 @@ func TestFileList_Add(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Add_Many(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||
@@ -92,15 +108,15 @@ func TestFileList_Add_Many(t *testing.T) {
|
||||
|
||||
func TestFileList_Exist(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
total, _ := list.Count()
|
||||
t.Log("total:", total)
|
||||
|
||||
@@ -130,7 +146,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
// 测试在多个数据库下的性能
|
||||
var listSlice = []caches.ListInterface{}
|
||||
for i := 1; i <= 10; i++ {
|
||||
list := caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||
var list = caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -138,6 +154,12 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
listSlice = append(listSlice, list)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, list := range listSlice {
|
||||
_ = list.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
var threads = 8
|
||||
wg.Add(threads)
|
||||
@@ -181,15 +203,16 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
|
||||
func TestFileList_CleanPrefix(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
err = list.CleanPrefix("123")
|
||||
if err != nil {
|
||||
@@ -200,15 +223,15 @@ func TestFileList_CleanPrefix(t *testing.T) {
|
||||
|
||||
func TestFileList_Remove(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
list.OnRemove(func(item *caches.Item) {
|
||||
t.Logf("remove %#v", item)
|
||||
})
|
||||
@@ -224,13 +247,15 @@ func TestFileList_Remove(t *testing.T) {
|
||||
|
||||
func TestFileList_Purge(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 count = 0
|
||||
_, err = list.Purge(caches.CountFileDB*2, func(hash string) error {
|
||||
@@ -246,13 +271,15 @@ func TestFileList_Purge(t *testing.T) {
|
||||
|
||||
func TestFileList_PurgeLFU(t *testing.T) {
|
||||
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()
|
||||
}()
|
||||
|
||||
err = list.IncreaseHit(stringutil.Md5("123456"))
|
||||
if err != nil {
|
||||
@@ -273,15 +300,16 @@ func TestFileList_PurgeLFU(t *testing.T) {
|
||||
|
||||
func TestFileList_Stat(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat, err := list.Stat(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -291,6 +319,11 @@ func TestFileList_Stat(t *testing.T) {
|
||||
|
||||
func TestFileList_Count(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -305,7 +338,12 @@ func TestFileList_Count(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_CleanAll(t *testing.T) {
|
||||
list := caches.NewFileList(Tea.Root + "/data")
|
||||
var list = caches.NewFileList(Tea.Root + "/data")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -320,6 +358,11 @@ func TestFileList_CleanAll(t *testing.T) {
|
||||
|
||||
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)
|
||||
@@ -333,7 +376,13 @@ func TestFileList_IncreaseHit(t *testing.T) {
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
for i := 0; i < 1000_000; i++ {
|
||||
|
||||
var count = 1_000_000
|
||||
|
||||
if !testutils.IsSingleTesting() {
|
||||
count = 10
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
||||
}
|
||||
if err != nil {
|
||||
@@ -344,6 +393,11 @@ func TestFileList_IncreaseHit(t *testing.T) {
|
||||
|
||||
func TestFileList_UpgradeV3(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -363,6 +417,11 @@ func TestFileList_UpgradeV3(t *testing.T) {
|
||||
|
||||
func BenchmarkFileList_Exist(b *testing.B) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
@@ -107,7 +108,9 @@ func TestMemoryList_Purge_Large_List(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Hour)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryList_Stat(t *testing.T) {
|
||||
@@ -255,9 +258,11 @@ func TestMemoryList_GC(t *testing.T) {
|
||||
//runtime.GC()
|
||||
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
|
||||
timeout := time.NewTimer(2 * time.Minute)
|
||||
<-timeout.C
|
||||
t.Log("2 minutes passed")
|
||||
if testutils.IsSingleTesting() {
|
||||
timeout := time.NewTimer(2 * time.Minute)
|
||||
<-timeout.C
|
||||
t.Log("2 minutes passed")
|
||||
|
||||
time.Sleep(30 * time.Minute)
|
||||
time.Sleep(30 * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -19,7 +21,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
events.OnClose(func() {
|
||||
remotelogs.Println("CACHE", "quiting cache manager")
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
||||
})
|
||||
@@ -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 {
|
||||
|
||||
@@ -52,9 +52,8 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
MaxKeys: 1,
|
||||
Id: 2,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
@@ -95,9 +94,9 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
|
||||
func TestManager_ChangePolicy_File(t *testing.T) {
|
||||
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/data/cache-index/p1",
|
||||
},
|
||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||
@@ -106,9 +105,9 @@ func TestManager_ChangePolicy_File(t *testing.T) {
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/data/cache-index/p1",
|
||||
},
|
||||
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -23,7 +24,9 @@ func TestNewOpenFileCache_Close(t *testing.T) {
|
||||
cache.Get("d.txt")
|
||||
cache.Close("a.txt")
|
||||
|
||||
time.Sleep(100 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(100 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOpenFileCache_CloseAll(t *testing.T) {
|
||||
|
||||
@@ -8,21 +8,29 @@ import (
|
||||
)
|
||||
|
||||
func TestFileReader(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, path, _ := storage.keyPath("my-key")
|
||||
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("file '" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
@@ -58,6 +66,10 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
||||
var path = "/Users/WorkSpace/EdgeProject/EdgeCache/p43/12/6b/126bbed90fc80f2bdfb19558948b0d49.cache"
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("'" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
@@ -66,6 +78,11 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
||||
var reader = NewFileReader(fp)
|
||||
err = reader.Init()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("file '" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf = make([]byte, 16*1024)
|
||||
@@ -79,13 +96,16 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileReader_Range(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -109,6 +129,10 @@ func TestFileReader_Range(t *testing.T) {
|
||||
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("'" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
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"
|
||||
@@ -21,9 +22,8 @@ import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@@ -53,22 +52,17 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileTmpSuffix = ".tmp"
|
||||
MinDiskSpace = 5 << 30 // 当前磁盘最小剩余空间
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileTmpSuffix = ".tmp"
|
||||
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
)
|
||||
|
||||
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 文件缓存
|
||||
//
|
||||
// 文件结构:
|
||||
@@ -77,7 +71,6 @@ type FileStorage struct {
|
||||
policy *serverconfigs.HTTPCachePolicy
|
||||
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
||||
memoryStorage *MemoryStorage // 一级缓存
|
||||
totalSize int64
|
||||
|
||||
list ListInterface
|
||||
locker sync.RWMutex
|
||||
@@ -92,7 +85,6 @@ type FileStorage struct {
|
||||
|
||||
openFileCache *OpenFileCache
|
||||
|
||||
mainDir string
|
||||
mainDiskIsFull bool
|
||||
|
||||
subDirs []*FileDir
|
||||
@@ -167,6 +159,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
IsFull: false,
|
||||
})
|
||||
}
|
||||
this.subDirs = subDirs
|
||||
this.checkDiskSpace()
|
||||
|
||||
err = newOptions.Init()
|
||||
@@ -203,6 +196,9 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
if newPolicy.PersistenceAutoPurgeInterval != this.policy.PersistenceAutoPurgeInterval {
|
||||
this.initPurgeTicker()
|
||||
}
|
||||
|
||||
// reset ignored keys
|
||||
this.ignoreKeys.Reset()
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
@@ -255,19 +251,6 @@ func (this *FileStorage) Init() error {
|
||||
}
|
||||
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||
this.list = list
|
||||
stat, err := list.Stat(func(hash string) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.totalSize = stat.Size
|
||||
this.list.OnAdd(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, item.TotalSize())
|
||||
})
|
||||
this.list.OnRemove(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
||||
})
|
||||
|
||||
// 检查目录是否存在
|
||||
_, err = os.Stat(dir)
|
||||
@@ -277,26 +260,24 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// 统计
|
||||
var count = stat.Count
|
||||
var size = stat.Size
|
||||
|
||||
var totalSize = this.TotalDiskSize()
|
||||
var cost = time.Since(before).Seconds() * 1000
|
||||
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
||||
if size > 1*sizes.G {
|
||||
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
|
||||
} else if size > 1*sizes.M {
|
||||
sizeMB = fmt.Sprintf("%.3f M", float64(size)/float64(sizes.M))
|
||||
} else if size > 1*sizes.K {
|
||||
sizeMB = fmt.Sprintf("%.3f K", float64(size)/float64(sizes.K))
|
||||
var sizeMB = types.String(totalSize) + " Bytes"
|
||||
if totalSize > 1*sizes.G {
|
||||
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G))
|
||||
} else if totalSize > 1*sizes.M {
|
||||
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M))
|
||||
} else if totalSize > 1*sizes.K {
|
||||
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K))
|
||||
}
|
||||
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
|
||||
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
|
||||
}()
|
||||
|
||||
// 初始化list
|
||||
@@ -319,6 +300,9 @@ func (this *FileStorage) Init() error {
|
||||
// 检查磁盘空间
|
||||
this.checkDiskSpace()
|
||||
|
||||
// clean *.trash directories
|
||||
this.cleanAllDeletedDirs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -430,18 +414,18 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
}
|
||||
|
||||
// 是否已忽略
|
||||
if this.ignoreKeys.Has(key) {
|
||||
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
|
||||
return nil, ErrEntityTooLarge
|
||||
}
|
||||
|
||||
// 先尝试内存缓存
|
||||
// 我们限定仅小文件优先存在内存中
|
||||
var maxMemorySize = FileToMemoryMaxSize
|
||||
if maxSize > maxMemorySize {
|
||||
if maxSize > 0 && maxSize < maxMemorySize {
|
||||
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
|
||||
@@ -462,9 +446,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()
|
||||
@@ -473,24 +457,14 @@ 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()
|
||||
}
|
||||
}()
|
||||
|
||||
// 检查是否超出最大值
|
||||
count, err := this.list.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys {
|
||||
return nil, NewCapacityError("write file cache failed: too many keys in cache storage")
|
||||
}
|
||||
// 检查是否超出容量
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
if capacityBytes > 0 && capacityBytes <= this.totalSize {
|
||||
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10))
|
||||
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize() {
|
||||
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + types.String(this.TotalDiskSize()) + " bytes, capacity: " + types.String(capacityBytes))
|
||||
}
|
||||
|
||||
var hash = stringutil.Md5(key)
|
||||
@@ -514,7 +488,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
|
||||
}
|
||||
@@ -582,7 +556,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 检查在各个系统中的稳定性
|
||||
@@ -596,13 +569,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() {
|
||||
@@ -652,7 +618,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
|
||||
}
|
||||
@@ -663,18 +631,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, -1, func() {
|
||||
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, maxSize, func() {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}), nil
|
||||
}
|
||||
@@ -788,7 +750,7 @@ func (this *FileStorage) CleanAll() error {
|
||||
return err
|
||||
}
|
||||
for _, info := range subDirs {
|
||||
subDir := info.Name()
|
||||
var subDir = info.Name()
|
||||
|
||||
// 检查目录名
|
||||
if !dirNameReg.MatchString(subDir) {
|
||||
@@ -796,7 +758,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
|
||||
@@ -807,7 +769,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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -899,7 +865,10 @@ func (this *FileStorage) Stop() {
|
||||
memoryStorage.Stop()
|
||||
})
|
||||
|
||||
_ = this.list.Reset()
|
||||
if this.list != nil {
|
||||
_ = this.list.Reset()
|
||||
}
|
||||
|
||||
if this.purgeTicker != nil {
|
||||
this.purgeTicker.Stop()
|
||||
}
|
||||
@@ -907,7 +876,9 @@ func (this *FileStorage) Stop() {
|
||||
this.hotTicker.Stop()
|
||||
}
|
||||
|
||||
_ = this.list.Close()
|
||||
if this.list != nil {
|
||||
_ = this.list.Close()
|
||||
}
|
||||
|
||||
var openFileCache = this.openFileCache
|
||||
if openFileCache != nil {
|
||||
@@ -919,7 +890,11 @@ func (this *FileStorage) Stop() {
|
||||
|
||||
// TotalDiskSize 消耗的磁盘尺寸
|
||||
func (this *FileStorage) TotalDiskSize() int64 {
|
||||
return atomic.LoadInt64(&this.totalSize)
|
||||
stat, err := fsutils.StatCache(this.options.Dir)
|
||||
if err == nil {
|
||||
return int64(stat.UsedSize())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// TotalMemorySize 内存尺寸
|
||||
@@ -932,8 +907,8 @@ func (this *FileStorage) TotalMemorySize() int64 {
|
||||
}
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
func (this *FileStorage) IgnoreKey(key string) {
|
||||
this.ignoreKeys.Push(key)
|
||||
func (this *FileStorage) IgnoreKey(key string, maxSize int64) {
|
||||
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
|
||||
}
|
||||
|
||||
// CanSendfile 是否支持Sendfile
|
||||
@@ -1059,8 +1034,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阈值时,多清理
|
||||
@@ -1254,7 +1236,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)
|
||||
@@ -1269,8 +1269,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
|
||||
}
|
||||
|
||||
@@ -1310,13 +1310,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 { // 控制数量
|
||||
@@ -1359,7 +1352,6 @@ func (this *FileStorage) createMemoryStorage() error {
|
||||
Name: this.policy.Name,
|
||||
Description: this.policy.Description,
|
||||
Capacity: this.options.MemoryPolicy.Capacity,
|
||||
MaxKeys: this.policy.MaxKeys,
|
||||
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
||||
Type: serverconfigs.CachePolicyStorageMemory,
|
||||
Options: this.policy.Options,
|
||||
@@ -1434,21 +1426,24 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
||||
|
||||
// 检查磁盘剩余空间
|
||||
func (this *FileStorage) checkDiskSpace() {
|
||||
if this.options != nil && len(this.options.Dir) > 0 {
|
||||
var stat unix.Statfs_t
|
||||
err := unix.Statfs(this.options.Dir, &stat)
|
||||
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.Stat(options.Dir)
|
||||
if err == nil {
|
||||
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
||||
this.mainDiskIsFull = availableBytes < MinDiskSpace
|
||||
this.mainDiskIsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
var stat unix.Statfs_t
|
||||
err := unix.Statfs(subDir.Path, &stat)
|
||||
stat, err := fsutils.Stat(subDir.Path)
|
||||
if err == nil {
|
||||
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
||||
subDir.IsFull = availableBytes < MinDiskSpace
|
||||
subDir.IsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func TestFileStorage_Init(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
@@ -26,6 +26,8 @@ func TestFileStorage_Init(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -40,17 +42,22 @@ func TestFileStorage_Init(t *testing.T) {
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
storage.purgeLoop()
|
||||
t.Log(storage.list.(*FileList).total, "entries left")
|
||||
t.Log(storage.list.(*FileList).Stat(func(hash string) bool {
|
||||
return true
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -95,6 +102,9 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -123,13 +133,16 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -146,7 +159,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"},
|
||||
@@ -188,13 +201,16 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -243,13 +259,16 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -299,13 +318,16 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -335,13 +357,16 @@ func TestFileStorage_Read(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -388,13 +413,16 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -421,13 +449,16 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Delete(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -440,13 +471,16 @@ func TestFileStorage_Delete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Stat(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -465,13 +499,16 @@ func TestFileStorage_Stat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_CleanAll(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -496,13 +533,16 @@ func TestFileStorage_CleanAll(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Stop(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -518,6 +558,9 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -528,6 +571,9 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
|
||||
func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||
var storage = NewFileStorage(nil)
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
||||
}
|
||||
|
||||
@@ -536,13 +582,16 @@ func BenchmarkFileStorage_Read(b *testing.B) {
|
||||
|
||||
_ = utils.SetRLimit(1024 * 1024)
|
||||
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -54,7 +54,7 @@ type StorageInterface interface {
|
||||
AddToList(item *Item)
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
IgnoreKey(key string)
|
||||
IgnoreKey(key string, maxSize int64)
|
||||
|
||||
// CanSendfile 是否支持Sendfile
|
||||
CanSendfile() bool
|
||||
|
||||
@@ -7,6 +7,7 @@ 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"
|
||||
@@ -111,8 +112,15 @@ func (this *MemoryStorage) Init() error {
|
||||
|
||||
// OpenReader 读取缓存
|
||||
func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
|
||||
hash := this.hash(key)
|
||||
var hash = this.hash(key)
|
||||
|
||||
// check if exists in list
|
||||
exists, _ := this.list.Exist(types.String(hash))
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// read from valuesMap
|
||||
this.locker.RLock()
|
||||
item := this.valuesMap[hash]
|
||||
if item == nil || !item.IsDone {
|
||||
@@ -121,7 +129,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
}
|
||||
|
||||
if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
|
||||
reader := NewMemoryReader(item)
|
||||
var reader = NewMemoryReader(item)
|
||||
err := reader.Init()
|
||||
if err != nil {
|
||||
this.locker.RUnlock()
|
||||
@@ -151,7 +159,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
|
||||
// OpenWriter 打开缓存写入器等待写入
|
||||
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||
if this.ignoreKeys.Has(key) {
|
||||
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
|
||||
return nil, ErrEntityTooLarge
|
||||
}
|
||||
|
||||
@@ -193,20 +201,22 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
}()
|
||||
|
||||
// 检查是否过期
|
||||
hash := this.hash(key)
|
||||
var hash = this.hash(key)
|
||||
item, ok := this.valuesMap[hash]
|
||||
if ok && !item.IsExpired() {
|
||||
return nil, ErrFileIsWriting
|
||||
var hashString = types.String(hash)
|
||||
exists, _ := this.list.Exist(hashString)
|
||||
if !exists {
|
||||
// remove from values map
|
||||
delete(this.valuesMap, hash)
|
||||
_ = this.list.Remove(hashString)
|
||||
item = nil
|
||||
} else {
|
||||
return nil, ErrFileIsWriting
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否超出最大值
|
||||
totalKeys, err := this.list.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if this.policy.MaxKeys > 0 && totalKeys > this.policy.MaxKeys {
|
||||
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
|
||||
}
|
||||
capacityBytes := this.memoryCapacityBytes()
|
||||
if bodySize < 0 {
|
||||
bodySize = 0
|
||||
@@ -216,7 +226,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
}
|
||||
|
||||
// 先删除
|
||||
err = this.deleteWithoutLocker(key)
|
||||
err := this.deleteWithoutLocker(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -353,6 +363,9 @@ func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy
|
||||
if newPolicy.CapacityBytes() == 0 {
|
||||
_ = this.CleanAll()
|
||||
}
|
||||
|
||||
// reset ignored keys
|
||||
this.ignoreKeys.Reset()
|
||||
}
|
||||
|
||||
// CanUpdatePolicy 检查策略是否可以更新
|
||||
@@ -383,8 +396,8 @@ func (this *MemoryStorage) TotalMemorySize() int64 {
|
||||
}
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
func (this *MemoryStorage) IgnoreKey(key string) {
|
||||
this.ignoreKeys.Push(key)
|
||||
func (this *MemoryStorage) IgnoreKey(key string, maxSize int64) {
|
||||
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
|
||||
}
|
||||
|
||||
// CanSendfile 是否支持Sendfile
|
||||
@@ -471,15 +484,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
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import "strings"
|
||||
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)
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (this *MemoryWriter) Write(data []byte) (n int, err error) {
|
||||
// 检查尺寸
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
this.storage.IgnoreKey(this.key)
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
return len(data), err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package caches
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"os"
|
||||
@@ -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,75 @@
|
||||
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())
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
package conns
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
@@ -10,14 +11,14 @@ import (
|
||||
var SharedMap = NewMap()
|
||||
|
||||
type Map struct {
|
||||
m map[string]map[int]net.Conn // ip => { port => Conn }
|
||||
m map[string]map[string]net.Conn // ip => { network_port => Conn }
|
||||
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMap() *Map {
|
||||
return &Map{
|
||||
m: map[string]map[int]net.Conn{},
|
||||
m: map[string]map[string]net.Conn{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,21 +26,19 @@ func (this *Map) Add(conn net.Conn) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
key, ip, ok := this.connAddr(conn)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var ip = tcpAddr.IP.String()
|
||||
var port = tcpAddr.Port
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
connMap, ok := this.m[ip]
|
||||
if !ok {
|
||||
this.m[ip] = map[int]net.Conn{port: conn}
|
||||
this.m[ip] = map[string]net.Conn{key: conn}
|
||||
} else {
|
||||
connMap[port] = conn
|
||||
connMap[key] = conn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +46,11 @@ func (this *Map) Remove(conn net.Conn) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
||||
key, ip, ok := this.connAddr(conn)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var ip = tcpAddr.IP.String()
|
||||
var port = tcpAddr.Port
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
@@ -62,7 +58,7 @@ func (this *Map) Remove(conn net.Conn) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(connMap, port)
|
||||
delete(connMap, key)
|
||||
|
||||
if len(connMap) == 0 {
|
||||
delete(this.m, ip)
|
||||
@@ -121,3 +117,24 @@ func (this *Map) AllConns() []net.Conn {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *Map) connAddr(conn net.Conn) (key string, ip string, ok bool) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var addr = conn.RemoteAddr()
|
||||
switch realAddr := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
|
||||
case *net.UDPAddr:
|
||||
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
|
||||
default:
|
||||
var s = addr.String()
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return addr.Network() + port, host, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.1.0"
|
||||
Version = "1.2.7"
|
||||
|
||||
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)
|
||||
|
||||
@@ -3,10 +3,11 @@ package events
|
||||
type Event = string
|
||||
|
||||
const (
|
||||
EventStart Event = "start" // start loading
|
||||
EventLoaded Event = "loaded" // first load
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
EventReload Event = "reload" // reload config
|
||||
EventTerminated Event = "terminated" // process terminated
|
||||
EventNFTablesReady Event = "nftablesReady" // nftables ready
|
||||
EventStart Event = "start" // start loading
|
||||
EventLoaded Event = "loaded" // first load
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
EventReload Event = "reload" // reload config
|
||||
EventTerminated Event = "terminated" // process terminated
|
||||
EventNFTablesReady Event = "nftablesReady" // nftables ready
|
||||
EventReloadSomeServers Event = "reloadSomeServers" // reload some servers
|
||||
)
|
||||
|
||||
@@ -24,6 +24,17 @@ func On(event Event, callback func()) {
|
||||
OnKey(event, nil, callback)
|
||||
}
|
||||
|
||||
func OnEvents(events []Event, callback func()) {
|
||||
for _, event := range events {
|
||||
On(event, callback)
|
||||
}
|
||||
}
|
||||
|
||||
func OnClose(callback func()) {
|
||||
On(EventQuit, callback)
|
||||
On(EventTerminated, callback)
|
||||
}
|
||||
|
||||
// OnKey 使用Key增加事件回调
|
||||
func OnKey(event Event, key interface{}, callback func()) {
|
||||
if key == nil {
|
||||
|
||||
@@ -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,13 +3,12 @@
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -32,7 +31,7 @@ func NewFirewalld() *Firewalld {
|
||||
cmdQueue: make(chan *firewalldCmd, 4096),
|
||||
}
|
||||
|
||||
path, err := exec.LookPath("firewall-cmd")
|
||||
path, err := executils.LookPath("firewall-cmd")
|
||||
if err == nil && len(path) > 0 {
|
||||
var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
|
||||
err := cmd.Run()
|
||||
@@ -195,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 {
|
||||
|
||||
@@ -6,12 +6,13 @@ package nftables_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConn_Test(t *testing.T) {
|
||||
_, err := exec.LookPath("nft")
|
||||
_, err := executils.LookPath("nft")
|
||||
if err == nil {
|
||||
t.Log("ok")
|
||||
return
|
||||
|
||||
@@ -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"
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
@@ -55,7 +54,7 @@ func init() {
|
||||
|
||||
// NftExePath 查找nftables可执行文件路径
|
||||
func NftExePath() string {
|
||||
path, _ := exec.LookPath("nft")
|
||||
path, _ := executils.LookPath("nft")
|
||||
if len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
@@ -86,21 +85,21 @@ func (this *Installer) Install() error {
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if len(NftExePath()) > 0 {
|
||||
if len(NftExePath()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmd *executils.Cmd
|
||||
|
||||
// check dnf
|
||||
dnfExe, err := exec.LookPath("dnf")
|
||||
dnfExe, err := executils.LookPath("dnf")
|
||||
if err == nil {
|
||||
cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables")
|
||||
}
|
||||
|
||||
// check apt
|
||||
if cmd == nil {
|
||||
aptExe, err := exec.LookPath("apt")
|
||||
aptExe, err := executils.LookPath("apt")
|
||||
if err == nil {
|
||||
cmd = executils.NewCmd(aptExe, "install", "nftables")
|
||||
}
|
||||
@@ -108,7 +107,7 @@ func (this *Installer) Install() error {
|
||||
|
||||
// check yum
|
||||
if cmd == nil {
|
||||
yumExe, err := exec.LookPath("yum")
|
||||
yumExe, err := executils.LookPath("yum")
|
||||
if err == nil {
|
||||
cmd = executils.NewCmd(yumExe, "-y", "install", "nftables")
|
||||
}
|
||||
@@ -122,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")
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
package goman_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
goman.New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
t.Log(goman.List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
t.Log(goman.List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
goman.NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
52
internal/goman/task_group.go
Normal file
52
internal/goman/task_group.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TaskGroup struct {
|
||||
semi chan zero.Zero
|
||||
wg *sync.WaitGroup
|
||||
locker *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewTaskGroup() *TaskGroup {
|
||||
var concurrent = runtime.NumCPU()
|
||||
if concurrent <= 1 {
|
||||
concurrent = 2
|
||||
}
|
||||
return &TaskGroup{
|
||||
semi: make(chan zero.Zero, concurrent),
|
||||
wg: &sync.WaitGroup{},
|
||||
locker: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Run(f func()) {
|
||||
this.wg.Add(1)
|
||||
go func() {
|
||||
defer this.wg.Done()
|
||||
|
||||
this.semi <- zero.Zero{}
|
||||
|
||||
f()
|
||||
|
||||
<-this.semi
|
||||
}()
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Wait() {
|
||||
this.wg.Wait()
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Lock() {
|
||||
this.locker.Lock()
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Unlock() {
|
||||
this.locker.Unlock()
|
||||
}
|
||||
30
internal/goman/task_group_test.go
Normal file
30
internal/goman/task_group_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package goman_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewTaskGroup(t *testing.T) {
|
||||
var group = goman.NewTaskGroup()
|
||||
var m = map[int]bool{}
|
||||
|
||||
for i := 0; i < runtime.NumCPU()*2; i++ {
|
||||
var index = i
|
||||
group.Run(func() {
|
||||
t.Log("task", index)
|
||||
|
||||
group.Lock()
|
||||
_, ok := m[index]
|
||||
if ok {
|
||||
t.Error("duplicated:", index)
|
||||
}
|
||||
m[index] = true
|
||||
group.Unlock()
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
@@ -82,7 +81,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
path := this.config.Path
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("firewall-cmd")
|
||||
path, err = executils.LookPath("firewall-cmd")
|
||||
if err != nil {
|
||||
if this.firewalldNotFound {
|
||||
return nil
|
||||
@@ -148,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,11 +2,11 @@ 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"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -55,7 +55,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
|
||||
// 创建ipset
|
||||
{
|
||||
path, err := exec.LookPath("ipset")
|
||||
path, err := executils.LookPath("ipset")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,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
|
||||
}
|
||||
@@ -89,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
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
|
||||
// firewalld
|
||||
if this.config.AutoAddToFirewalld {
|
||||
path, err := exec.LookPath("firewall-cmd")
|
||||
path, err := executils.LookPath("firewall-cmd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,14 +172,14 @@ 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iptables
|
||||
if this.config.AutoAddToIPTables {
|
||||
path, err := exec.LookPath("iptables")
|
||||
path, err := executils.LookPath("iptables")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,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 {
|
||||
@@ -311,7 +311,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
var path = this.config.Path
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("ipset")
|
||||
path, err = executils.LookPath("ipset")
|
||||
if err != nil {
|
||||
// 找不到ipset命令错误只提示一次
|
||||
if this.ipsetNotfound {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
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"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -87,7 +86,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
||||
var path = this.config.Path
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("iptables")
|
||||
path, err = executils.LookPath("iptables")
|
||||
if err != nil {
|
||||
if this.iptablesNotFound {
|
||||
return nil
|
||||
@@ -129,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
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ ON "` + this.itemTableName + `" (
|
||||
this.db = db
|
||||
|
||||
goman.New(func() {
|
||||
events.On(events.EventQuit, func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ func init() {
|
||||
SharedIPListManager.Start()
|
||||
})
|
||||
})
|
||||
events.On(events.EventQuit, func() {
|
||||
events.OnClose(func() {
|
||||
SharedIPListManager.Stop()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ func TestIPListManager_check(t *testing.T) {
|
||||
func TestIPListManager_loop(t *testing.T) {
|
||||
manager := NewIPListManager()
|
||||
manager.Start()
|
||||
manager.pageSize = 10
|
||||
err := manager.loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -18,7 +18,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
events.OnClose(func() {
|
||||
SharedManager.Quit()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"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"
|
||||
"testing"
|
||||
@@ -79,6 +80,10 @@ func TestTask_Add(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTask_Add_Many(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -51,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
|
||||
}
|
||||
@@ -77,7 +80,7 @@ func (this *APIStream) loop() error {
|
||||
if this.isQuiting {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
return err
|
||||
}
|
||||
this.stream = nodeStream
|
||||
|
||||
@@ -93,7 +96,7 @@ func (this *APIStream) loop() error {
|
||||
remotelogs.Println("API_STREAM", "quit")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@@ -336,7 +339,7 @@ func (this *APIStream) handleNewNodeTask(message *pb.NodeStreamMessage) error {
|
||||
|
||||
// 检查Systemd服务
|
||||
func (this *APIStream) handleCheckSystemdService(message *pb.NodeStreamMessage) error {
|
||||
systemctl, err := exec.LookPath("systemctl")
|
||||
systemctl, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "'systemctl' not found")
|
||||
return nil
|
||||
@@ -378,7 +381,7 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
|
||||
return nil
|
||||
}
|
||||
|
||||
nft, err := exec.LookPath("nft")
|
||||
nft, err := executils.LookPath("nft")
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "'nft' not found: "+err.Error())
|
||||
return nil
|
||||
@@ -439,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,16 +3,16 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var synFloodCounter = counters.NewCounter().WithGC()
|
||||
|
||||
// ClientConn 客户端连接
|
||||
type ClientConn struct {
|
||||
BaseClientConn
|
||||
@@ -61,11 +63,16 @@ func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool
|
||||
isTLS: isTLS,
|
||||
isHTTP: isHTTP,
|
||||
isLO: strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:"),
|
||||
isNoStat: connutils.IsNoStatConn(rawConn.RemoteAddr().String()),
|
||||
isNoStat: connutils.IsNoStatConn(remoteAddr),
|
||||
isInAllowList: isInAllowList,
|
||||
createdAt: fasttime.Now().Unix(),
|
||||
}
|
||||
|
||||
if existsLnNodeIP(conn.RawIP()) {
|
||||
conn.SetIsPersistent(true)
|
||||
}
|
||||
|
||||
// 超时等设置
|
||||
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||
if globalServerConfig != nil {
|
||||
var performanceConfig = globalServerConfig.Performance
|
||||
@@ -91,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
|
||||
}
|
||||
@@ -122,14 +129,13 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
// 检测是否为超时错误
|
||||
var isTimeout = err != nil && os.IsTimeout(err)
|
||||
var isHandshakeError = isTimeout && !this.hasRead
|
||||
if isTimeout {
|
||||
_ = this.SetLinger(0)
|
||||
} else {
|
||||
|
||||
if err != nil {
|
||||
_ = this.SetLinger(nodeconfigs.DefaultTCPLinger)
|
||||
}
|
||||
|
||||
// 忽略白名单和局域网
|
||||
if this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
|
||||
if !this.isPersistent && this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
|
||||
// SYN Flood检测
|
||||
if this.serverId == 0 || !this.hasResetSYNFlood {
|
||||
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
|
||||
@@ -157,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
|
||||
}
|
||||
@@ -165,8 +171,7 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// 设置写超时时间
|
||||
if this.autoWriteTimeout {
|
||||
// TODO L2 -> L1 写入时不限制时间
|
||||
if !this.isPersistent && this.autoWriteTimeout {
|
||||
var timeoutSeconds = len(b) / 1024
|
||||
if timeoutSeconds < 3 {
|
||||
timeoutSeconds = 3
|
||||
@@ -183,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绑定之前的带宽
|
||||
@@ -285,14 +292,13 @@ func (this *ClientConn) LastErr() error {
|
||||
}
|
||||
|
||||
func (this *ClientConn) resetSYNFlood() {
|
||||
ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
|
||||
synFloodCounter.ResetKey("SYN_FLOOD:" + this.RawIP())
|
||||
}
|
||||
|
||||
func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) {
|
||||
var ip = this.RawIP()
|
||||
if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
|
||||
var timestamp = fasttime.Now().UnixNextMinute()
|
||||
var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp, true)
|
||||
var result = synFloodCounter.IncreaseKey("SYN_FLOOD:"+ip, 60)
|
||||
var minAttempts = synFloodConfig.MinAttempts
|
||||
if minAttempts < 5 {
|
||||
minAttempts = 5
|
||||
@@ -301,7 +307,7 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
|
||||
// 非TLS,设置为两倍,防止误封
|
||||
minAttempts = 2 * minAttempts
|
||||
}
|
||||
if result >= int64(minAttempts) {
|
||||
if result >= types.Uint64(minAttempts) {
|
||||
var timeout = synFloodConfig.TimeoutSeconds
|
||||
if timeout <= 0 {
|
||||
timeout = 600
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BaseClientConn struct {
|
||||
@@ -24,6 +26,8 @@ type BaseClientConn struct {
|
||||
isClosed bool
|
||||
|
||||
rawIP string
|
||||
|
||||
totalSentBytes int64
|
||||
}
|
||||
|
||||
func (this *BaseClientConn) IsClosed() bool {
|
||||
@@ -54,12 +58,13 @@ func (this *BaseClientConn) SetServerId(serverId int64) (goNext bool) {
|
||||
goNext = true
|
||||
|
||||
// 检查服务相关IP黑名单
|
||||
if serverId > 0 && len(this.rawIP) > 0 {
|
||||
var rawIP = this.RawIP()
|
||||
if serverId > 0 && len(rawIP) > 0 {
|
||||
// 是否在白名单中
|
||||
ok, _, expiresAt := iplibrary.AllowIP(this.rawIP, serverId)
|
||||
ok, _, expiresAt := iplibrary.AllowIP(rawIP, serverId)
|
||||
if !ok {
|
||||
_ = this.rawConn.Close()
|
||||
firewalls.DropTemporaryTo(this.rawIP, expiresAt)
|
||||
firewalls.DropTemporaryTo(rawIP, expiresAt)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -123,8 +128,8 @@ func (this *BaseClientConn) TCPConn() (tcpConn *net.TCPConn, ok bool) {
|
||||
switch conn := this.rawConn.(type) {
|
||||
case *tls.Conn:
|
||||
var internalConn = conn.NetConn()
|
||||
clientConn, ok := internalConn.(*ClientConn)
|
||||
if ok {
|
||||
clientConn, isClientConn := internalConn.(*ClientConn)
|
||||
if isClientConn {
|
||||
return clientConn.TCPConn()
|
||||
}
|
||||
tcpConn, ok = internalConn.(*net.TCPConn)
|
||||
@@ -145,6 +150,8 @@ func (this *BaseClientConn) SetLinger(seconds int) error {
|
||||
|
||||
func (this *BaseClientConn) SetIsPersistent(isPersistent bool) {
|
||||
this.isPersistent = isPersistent
|
||||
|
||||
_ = this.rawConn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
// SetFingerprint 设置指纹信息
|
||||
@@ -156,3 +163,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
|
||||
}
|
||||
|
||||
@@ -32,4 +32,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"
|
||||
@@ -19,8 +21,10 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -41,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
|
||||
}
|
||||
|
||||
@@ -52,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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,21 +113,29 @@ func (this *HTTPCacheTaskManager) Loop() error {
|
||||
|
||||
var pbResults = []*pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{}
|
||||
|
||||
var taskGroup = goman.NewTaskGroup()
|
||||
for _, key := range keys {
|
||||
err = this.processKey(key)
|
||||
var taskKey = key
|
||||
taskGroup.Run(func() {
|
||||
processErr := this.processKey(taskKey)
|
||||
var pbResult = &pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{
|
||||
Id: taskKey.Id,
|
||||
NodeClusterId: taskKey.NodeClusterId,
|
||||
Error: "",
|
||||
}
|
||||
|
||||
var pbResult = &pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{
|
||||
Id: key.Id,
|
||||
NodeClusterId: key.NodeClusterId,
|
||||
Error: "",
|
||||
}
|
||||
if processErr != nil {
|
||||
pbResult.Error = processErr.Error()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
pbResult.Error = err.Error()
|
||||
}
|
||||
pbResults = append(pbResults, pbResult)
|
||||
taskGroup.Lock()
|
||||
pbResults = append(pbResults, pbResult)
|
||||
taskGroup.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
taskGroup.Wait()
|
||||
|
||||
_, err = rpcClient.HTTPCacheTaskKeyRPC.UpdateHTTPCacheTaskKeysStatus(rpcClient.Context(), &pb.UpdateHTTPCacheTaskKeysStatusRequest{KeyResults: pbResults})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -224,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) {
|
||||
@@ -233,29 +222,103 @@ 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 {
|
||||
return errors.New("request failed: " + fullKey + ": " + err.Error())
|
||||
err = this.simplifyErr(err)
|
||||
return fmt.Errorf("request failed: '%s': %w", fullKey, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
// 读取内容,以便于生成缓存
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
|
||||
// 处理502
|
||||
if resp.StatusCode == http.StatusBadGateway {
|
||||
return errors.New("read origin site timeout")
|
||||
}
|
||||
|
||||
// 读取内容,以便于生成缓存
|
||||
_, err = io.Copy(io.Discard, resp.Body)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
err = this.simplifyErr(err)
|
||||
return fmt.Errorf("request failed: '%s': %w", fullKey, err)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPCacheTaskManager) simplifyErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if os.IsTimeout(err) {
|
||||
return errors.New("timeout to read origin site")
|
||||
}
|
||||
|
||||
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,8 +6,9 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"golang.org/x/net/http2"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
@@ -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
|
||||
@@ -131,14 +143,8 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
var transport = &HTTPClientTransport{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// 支持TOA的连接
|
||||
conn, err := this.handleTOA(req, ctx, network, originAddr, connectionTimeout)
|
||||
if conn != nil || err != nil {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// 普通的连接
|
||||
conn, err = (&net.Dialer{
|
||||
conn, err := (&net.Dialer{
|
||||
Timeout: connectionTimeout,
|
||||
KeepAlive: 1 * time.Minute,
|
||||
}).DialContext(ctx, network, originAddr)
|
||||
@@ -152,7 +158,7 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
return NewOriginConn(conn), nil
|
||||
},
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: idleConns,
|
||||
@@ -166,6 +172,11 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
},
|
||||
}
|
||||
|
||||
// support http/2
|
||||
if origin.HTTP2Enabled && origin.Addr != nil && origin.Addr.Protocol == serverconfigs.ProtocolHTTPS {
|
||||
_ = http2.ConfigureTransport(transport.Transport)
|
||||
}
|
||||
|
||||
rawClient = &http.Client{
|
||||
Timeout: readTimeout,
|
||||
Transport: transport,
|
||||
@@ -196,54 +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()
|
||||
}
|
||||
}
|
||||
|
||||
// 支持TOA
|
||||
func (this *HTTPClientPool) handleTOA(req *HTTPRequest, ctx context.Context, network string, originAddr string, connectionTimeout time.Duration) (net.Conn, error) {
|
||||
// TODO 每个服务读取自身所属集群的TOA设置
|
||||
var toaConfig = sharedTOAManager.Config()
|
||||
if toaConfig != nil && toaConfig.IsOn {
|
||||
var retries = 3
|
||||
for i := 1; i <= retries; i++ {
|
||||
var port = int(toaConfig.RandLocalPort())
|
||||
// TODO 思考是否支持X-Real-IP/X-Forwarded-IP
|
||||
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + req.requestRemoteAddr(true))
|
||||
if err != nil {
|
||||
remotelogs.Error("TOA", "add failed: "+err.Error())
|
||||
} else {
|
||||
dialer := net.Dialer{
|
||||
Timeout: connectionTimeout,
|
||||
KeepAlive: 1 * time.Minute,
|
||||
LocalAddr: &net.TCPAddr{
|
||||
Port: port,
|
||||
},
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, network, originAddr)
|
||||
// TODO 需要在合适的时机删除TOA记录
|
||||
if err == nil || i == retries {
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 支持PROXY Protocol
|
||||
func (this *HTTPClientPool) handlePROXYProtocol(conn net.Conn, req *HTTPRequest, proxyProtocol *serverconfigs.ProxyProtocolConfig) error {
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ type HTTPRequest struct {
|
||||
ServerAddr string // 实际启动的服务器监听地址
|
||||
IsHTTP bool
|
||||
IsHTTPS bool
|
||||
IsHTTP3 bool
|
||||
|
||||
// 共享参数
|
||||
nodeConfig *nodeconfigs.NodeConfig
|
||||
@@ -388,6 +389,21 @@ func (this *HTTPRequest) doEnd() {
|
||||
// 流量统计
|
||||
// TODO 增加是否开启开关
|
||||
if this.ReqServer != nil && this.ReqServer.Id > 0 {
|
||||
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
|
||||
|
||||
@@ -396,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() {
|
||||
@@ -479,6 +498,17 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
|
||||
// remote addr
|
||||
if web.RemoteAddr != nil && (web.RemoteAddr.IsPrior || isTop) && web.RemoteAddr.IsOn {
|
||||
this.web.RemoteAddr = web.RemoteAddr
|
||||
|
||||
// check if from proxy
|
||||
if len(this.web.RemoteAddr.Value) > 0 && this.web.RemoteAddr.Value != "${rawRemoteAddr}" {
|
||||
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if requestConn != nil {
|
||||
requestClientConn, ok := requestConn.(ClientConnInterface)
|
||||
if ok {
|
||||
requestClientConn.SetIsPersistent(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// charset
|
||||
@@ -497,6 +527,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
|
||||
this.web.Compression = web.Compression
|
||||
}
|
||||
|
||||
// optimizer
|
||||
if web.Optimization != nil && (web.Optimization.IsPrior || (isTop && web.Optimization.IsOn())) {
|
||||
this.web.Optimization = web.Optimization
|
||||
}
|
||||
|
||||
// webp
|
||||
if web.WebP != nil && (web.WebP.IsPrior || isTop) {
|
||||
this.web.WebP = web.WebP
|
||||
@@ -1698,8 +1733,13 @@ func (this *HTTPRequest) fixRequestHeader(header http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理自定义Response Header
|
||||
func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, statusCode int) {
|
||||
// ProcessResponseHeaders 处理自定义Response Header
|
||||
func (this *HTTPRequest) ProcessResponseHeaders(responseHeader http.Header, statusCode int) {
|
||||
// Server Name
|
||||
if this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && len(this.nodeConfig.GlobalServerConfig.HTTPAll.ServerName) > 0 {
|
||||
responseHeader.Set("Server", this.nodeConfig.GlobalServerConfig.HTTPAll.ServerName)
|
||||
}
|
||||
|
||||
// 删除/添加/替换Header
|
||||
// TODO 实现AddTrailers
|
||||
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {
|
||||
@@ -1828,6 +1868,11 @@ func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, stat
|
||||
this.ReqServer.HTTPS.SSLPolicy.HSTS.Match(this.ReqHost) {
|
||||
responseHeader.Set(this.ReqServer.HTTPS.SSLPolicy.HSTS.HeaderKey(), this.ReqServer.HTTPS.SSLPolicy.HSTS.HeaderValue())
|
||||
}
|
||||
|
||||
// HTTP/3
|
||||
if this.IsHTTPS && !this.IsHTTP3 && this.ReqServer.SupportsHTTP3() {
|
||||
this.processHTTP3Headers(responseHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加错误信息
|
||||
@@ -1897,7 +1942,7 @@ func (this *HTTPRequest) canIgnore(err error) bool {
|
||||
|
||||
// 检查连接是否已关闭
|
||||
func (this *HTTPRequest) isConnClosed() bool {
|
||||
requestConn := this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if requestConn == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -38,9 +38,14 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
|
||||
// 添加 X-Cache Header
|
||||
var addStatusHeader = this.web.Cache.AddStatusHeader
|
||||
var cacheBypassDescription = ""
|
||||
if addStatusHeader {
|
||||
defer func() {
|
||||
cacheStatus := this.varMapping["cache.status"]
|
||||
if len(cacheBypassDescription) > 0 {
|
||||
this.writer.Header().Set("X-Cache", cacheBypassDescription)
|
||||
return
|
||||
}
|
||||
var cacheStatus = this.varMapping["cache.status"]
|
||||
if cacheStatus != "HIT" {
|
||||
this.writer.Header().Set("X-Cache", cacheStatus)
|
||||
}
|
||||
@@ -48,7 +53,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
}
|
||||
|
||||
// 检查服务独立的缓存条件
|
||||
refType := ""
|
||||
var refType = ""
|
||||
for _, cacheRef := range this.web.Cache.CacheRefs {
|
||||
if !cacheRef.IsOn {
|
||||
continue
|
||||
@@ -85,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 {
|
||||
@@ -94,6 +106,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
// 校验请求
|
||||
if !this.cacheRef.MatchRequest(this.RawReq) {
|
||||
this.cacheRef = nil
|
||||
cacheBypassDescription = "BYPASS, not match"
|
||||
return
|
||||
}
|
||||
|
||||
@@ -106,6 +119,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
if this.cacheRef.EnableRequestCachePragma {
|
||||
if this.RawReq.Header.Get("Cache-Control") == "no-cache" || this.RawReq.Header.Get("Pragma") == "no-cache" {
|
||||
this.cacheRef = nil
|
||||
cacheBypassDescription = "BYPASS, Cache-Control or Pragma"
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -119,6 +133,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
var key = this.Format(this.cacheRef.Key)
|
||||
if len(key) == 0 {
|
||||
this.cacheRef = nil
|
||||
cacheBypassDescription = "BYPASS, empty key"
|
||||
return
|
||||
}
|
||||
var method = this.Method()
|
||||
@@ -131,9 +146,10 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
this.varMapping["cache.key"] = key
|
||||
|
||||
// 读取缓存
|
||||
storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
|
||||
var storage = caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
|
||||
if storage == nil {
|
||||
this.cacheRef = nil
|
||||
cacheBypassDescription = "BYPASS, no policy found"
|
||||
return
|
||||
}
|
||||
this.writer.cacheStorage = storage
|
||||
@@ -241,16 +257,19 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
reader, err = storage.OpenReader(key, useStale, false)
|
||||
if err != nil && this.cacheRef.AllowPartialContent {
|
||||
// 尝试读取分片的缓存内容
|
||||
if len(rangeHeader) == 0 {
|
||||
if len(rangeHeader) == 0 && this.cacheRef.ForcePartialContent {
|
||||
// 默认读取开头
|
||||
rangeHeader = "bytes=0-"
|
||||
}
|
||||
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader)
|
||||
if pReader != nil {
|
||||
isPartialCache = true
|
||||
reader = pReader
|
||||
partialRanges = ranges
|
||||
err = nil
|
||||
|
||||
if len(rangeHeader) > 0 {
|
||||
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader)
|
||||
if pReader != nil {
|
||||
isPartialCache = true
|
||||
reader = pReader
|
||||
partialRanges = ranges
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,13 +320,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
this.writer.SetSentHeaderBytes(reader.HeaderSize())
|
||||
var headerPool = this.bytePool(reader.HeaderSize())
|
||||
var headerBuf = headerPool.Get()
|
||||
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, err error) {
|
||||
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, readErr error) {
|
||||
headerData = append(headerData, headerBuf[:n]...)
|
||||
for {
|
||||
nIndex := bytes.Index(headerData, []byte{'\n'})
|
||||
var nIndex = bytes.Index(headerData, []byte{'\n'})
|
||||
if nIndex >= 0 {
|
||||
row := headerData[:nIndex]
|
||||
spaceIndex := bytes.Index(row, []byte{':'})
|
||||
var row = headerData[:nIndex]
|
||||
var spaceIndex = bytes.Index(row, []byte{':'})
|
||||
if spaceIndex <= 0 {
|
||||
return false, errors.New("invalid header '" + string(row) + "'")
|
||||
}
|
||||
@@ -375,7 +394,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
// 支持 If-None-Match
|
||||
if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.addExpiresHeader(reader.ExpiresAt())
|
||||
this.writer.WriteHeader(http.StatusNotModified)
|
||||
this.isCached = true
|
||||
@@ -387,7 +406,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
// 支持 If-Modified-Since
|
||||
if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.addExpiresHeader(reader.ExpiresAt())
|
||||
this.writer.WriteHeader(http.StatusNotModified)
|
||||
this.isCached = true
|
||||
@@ -396,7 +415,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), reader.Status())
|
||||
this.ProcessResponseHeaders(this.writer.Header(), reader.Status())
|
||||
this.addExpiresHeader(reader.ExpiresAt())
|
||||
|
||||
// 返回上级节点过期时间
|
||||
@@ -425,7 +444,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
if supportRange {
|
||||
if len(rangeHeader) > 0 {
|
||||
if fileSize == 0 {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -433,7 +452,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
if len(ranges) == 0 {
|
||||
ranges, ok = httpRequestParseRangeHeader(rangeHeader)
|
||||
if !ok {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -442,7 +461,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
for k, r := range ranges {
|
||||
r2, ok := r.Convert(fileSize)
|
||||
if !ok {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -460,9 +479,9 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) {
|
||||
_, err = this.writer.Write(bodyBuf[:n])
|
||||
if err != nil {
|
||||
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, readErr error) {
|
||||
_, readErr = this.writer.Write(bodyBuf[:n])
|
||||
if readErr != nil {
|
||||
return false, errWritingToClient
|
||||
}
|
||||
return true, nil
|
||||
@@ -472,7 +491,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
this.varMapping["cache.status"] = "MISS"
|
||||
|
||||
if err == caches.ErrInvalidRange {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -485,7 +504,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
var boundary = httpRequestGenBoundary()
|
||||
respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
|
||||
respHeader.Del("Content-Length")
|
||||
contentType := respHeader.Get("Content-Type")
|
||||
var contentType = respHeader.Get("Content-Type")
|
||||
|
||||
this.writer.WriteHeader(http.StatusPartialContent)
|
||||
|
||||
@@ -516,9 +535,9 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
err := reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, err error) {
|
||||
_, err = this.writer.Write(bodyBuf[:n])
|
||||
if err != nil {
|
||||
err = reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, readErr error) {
|
||||
_, readErr = this.writer.Write(bodyBuf[:n])
|
||||
if readErr != nil {
|
||||
return false, errWritingToClient
|
||||
}
|
||||
return true, nil
|
||||
|
||||
@@ -57,7 +57,7 @@ func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage s
|
||||
return "${" + varName + "}"
|
||||
})
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.writer.WriteHeader(statusCode)
|
||||
|
||||
_, _ = this.writer.Write([]byte(pageContent))
|
||||
@@ -110,7 +110,7 @@ func (this *HTTPRequest) write50x(err error, statusCode int, enMessage string, z
|
||||
return "${" + varName + "}"
|
||||
})
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.writer.WriteHeader(statusCode)
|
||||
|
||||
_, _ = this.writer.Write([]byte(pageContent))
|
||||
|
||||
@@ -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)
|
||||
@@ -197,7 +197,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
||||
|
||||
// 响应Header
|
||||
this.writer.AddHeaders(resp.Header)
|
||||
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||
|
||||
// 准备
|
||||
this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
var fullURL = ""
|
||||
var fullURL string
|
||||
if u.BeforeHasQuery() {
|
||||
fullURL = this.URL()
|
||||
} else {
|
||||
@@ -54,7 +54,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
} else { // 精准匹配
|
||||
@@ -119,7 +119,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
|
||||
// 参数
|
||||
var qIndex = strings.Index(this.uri, "?")
|
||||
@@ -194,7 +194,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
var containsPort = false
|
||||
var containsPort bool
|
||||
if u.PortsAll {
|
||||
containsPort = true
|
||||
} else {
|
||||
@@ -211,7 +211,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.processResponseHeaders(this.writer.Header(), status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, status)
|
||||
return true
|
||||
}
|
||||
|
||||
10
internal/nodes/http_request_http3.go
Normal file
10
internal/nodes/http_request_http3.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package nodes
|
||||
|
||||
import "net/http"
|
||||
|
||||
func (this *HTTPRequest) processHTTP3Headers(respHeader http.Header) {
|
||||
// stub
|
||||
}
|
||||
@@ -12,6 +12,10 @@ const (
|
||||
LNExpiresHeader = "X-Edge-Ln-Expires"
|
||||
)
|
||||
|
||||
func existsLnNodeIP(nodeIP string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) checkLnRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ const (
|
||||
|
||||
// 日志
|
||||
func (this *HTTPRequest) log() {
|
||||
// 检查全局配置
|
||||
if this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && !this.nodeConfig.GlobalServerConfig.HTTPAccessLog.IsOn {
|
||||
return
|
||||
}
|
||||
|
||||
var ref *serverconfigs.HTTPAccessLogRef
|
||||
if !this.forceLog {
|
||||
if this.disableLog {
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -32,7 +34,29 @@ func (this *HTTPRequest) doMismatch() {
|
||||
}
|
||||
|
||||
// 根据配置进行相应的处理
|
||||
if sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
|
||||
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
|
||||
var statusCode = 404
|
||||
var httpAllConfig = globalServerConfig.HTTPAll
|
||||
var mismatchAction = httpAllConfig.DomainMismatchAction
|
||||
|
||||
if mismatchAction != nil && mismatchAction.Options != nil {
|
||||
var mismatchStatusCode = mismatchAction.Options.GetInt("statusCode")
|
||||
if mismatchStatusCode > 0 && mismatchStatusCode >= 100 && mismatchStatusCode < 1000 {
|
||||
statusCode = mismatchStatusCode
|
||||
}
|
||||
}
|
||||
|
||||
// 是否正在访问IP
|
||||
if globalServerConfig.HTTPAll.NodeIPShowPage && net.ParseIP(this.ReqHost) != nil {
|
||||
var contentHTML = this.Format(globalServerConfig.HTTPAll.NodeIPPageHTML)
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
|
||||
this.writer.WriteHeader(statusCode)
|
||||
_, _ = this.writer.WriteString(contentHTML)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查cc
|
||||
// TODO 可以在管理端配置是否开启以及最多尝试次数
|
||||
// 要考虑到服务在切换集群时,域名未生效状态时,用户访问的仍然是老集群中的节点,就会产生找不到域名的情况
|
||||
@@ -47,13 +71,13 @@ func (this *HTTPRequest) doMismatch() {
|
||||
}
|
||||
|
||||
// 处理当前连接
|
||||
var httpAllConfig = sharedNodeConfig.GlobalServerConfig.HTTPAll
|
||||
var mismatchAction = httpAllConfig.DomainMismatchAction
|
||||
if mismatchAction != nil && mismatchAction.Code == "page" {
|
||||
if mismatchAction.Options != nil {
|
||||
var contentHTML = this.Format(mismatchAction.Options.GetString("contentHTML"))
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.WriteHeader(mismatchAction.Options.GetInt("statusCode"))
|
||||
_, _ = this.writer.Write([]byte(mismatchAction.Options.GetString("contentHTML")))
|
||||
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
|
||||
this.writer.WriteHeader(statusCode)
|
||||
_, _ = this.writer.Write([]byte(contentHTML))
|
||||
} else {
|
||||
http.Error(this.writer, "404 page not found: '"+this.URL()+"'", http.StatusNotFound)
|
||||
}
|
||||
|
||||
15
internal/nodes/http_request_oss.go
Normal file
15
internal/nodes/http_request_oss.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (this *HTTPRequest) doOSSOrigin(origin *serverconfigs.OriginConfig) (resp *http.Response, goNext bool, errorCode string, ossBucketName string, err error) {
|
||||
// stub
|
||||
return nil, false, "", "", errors.New("not implemented")
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var urlPrefixRegexp = regexp.MustCompile("^(?i)(http|https|ftp)://")
|
||||
const defaultPageContentType = "text/html; charset=utf-8"
|
||||
|
||||
// 请求特殊页面
|
||||
func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
||||
@@ -49,7 +49,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
for _, page := range pages {
|
||||
if page.Match(status) {
|
||||
if len(page.BodyType) == 0 || page.BodyType == shared.BodyTypeURL {
|
||||
if urlPrefixRegexp.MatchString(page.URL) {
|
||||
if urlSchemeRegexp.MatchString(page.URL) {
|
||||
var newStatus = page.NewStatus
|
||||
if newStatus <= 0 {
|
||||
newStatus = status
|
||||
@@ -57,45 +57,50 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
this.doURL(http.MethodGet, page.URL, "", newStatus, true)
|
||||
return true
|
||||
} else {
|
||||
file := Tea.Root + Tea.DS + page.URL
|
||||
fp, err := os.Open(file)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
msg := "404 page not found: '" + page.URL + "'"
|
||||
|
||||
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)
|
||||
_, err := this.writer.Write([]byte(msg))
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
}
|
||||
var file = Tea.Root + Tea.DS + realpath
|
||||
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
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
msg := "404 could not read page content: '" + page.URL + "'"
|
||||
|
||||
var msg = "404 could not read page content: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, err := this.writer.Write([]byte(msg))
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
}
|
||||
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.processResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||
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.processResponseHeaders(this.writer.Header(), status)
|
||||
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)
|
||||
}
|
||||
buf := utils.BytePool1k.Get()
|
||||
var buf = utils.BytePool1k.Get()
|
||||
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
|
||||
return []byte(this.Format(string(p)))
|
||||
})
|
||||
@@ -107,10 +112,6 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
} else {
|
||||
this.writer.SetOk()
|
||||
}
|
||||
err = fp.Close()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -126,11 +127,13 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.processResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||
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.processResponseHeaders(this.writer.Header(), status)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func (this *HTTPRequest) doPlanExpires() {
|
||||
this.tags = append(this.tags, "plan")
|
||||
|
||||
var statusCode = http.StatusNotFound
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
|
||||
this.writer.WriteHeader(statusCode)
|
||||
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))
|
||||
|
||||
@@ -42,7 +42,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
|
||||
}
|
||||
|
||||
var newURL = "https://" + host + this.RawReq.RequestURI
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
|
||||
|
||||
return true
|
||||
|
||||
@@ -12,13 +12,29 @@ func (this *HTTPRequest) doCheckReferers() (shouldStop bool) {
|
||||
return
|
||||
}
|
||||
|
||||
var origin = this.RawReq.Header.Get("Origin")
|
||||
|
||||
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
|
||||
|
||||
// 处理用到Origin的特殊功能
|
||||
if this.web.Referers.CheckOrigin && len(origin) > 0 {
|
||||
// 处理Websocket
|
||||
if this.web.Websocket != nil && this.web.Websocket.IsOn && this.RawReq.Header.Get("Upgrade") == "websocket" {
|
||||
originHost, _ := httpParseHost(origin)
|
||||
if len(originHost) > 0 && this.web.Websocket.MatchOrigin(originHost) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var refererURL = this.RawReq.Header.Get("Referer")
|
||||
if len(refererURL) == 0 && this.web.Referers.CheckOrigin {
|
||||
var origin = this.RawReq.Header.Get("Origin")
|
||||
if len(origin) > 0 && origin != "null" {
|
||||
refererURL = "https://" + origin // 因为Origin都只有域名部分,所以为了下面的URL 分析需要加上https://
|
||||
if urlSchemeRegexp.MatchString(origin) {
|
||||
refererURL = origin
|
||||
} else {
|
||||
refererURL = "https://" + origin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,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 +41,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
|
||||
@@ -66,19 +64,21 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
|
||||
// 二级节点
|
||||
var hasMultipleLnNodes = false
|
||||
if this.cacheRef != nil {
|
||||
if this.cacheRef != nil || (this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && this.nodeConfig.GlobalServerConfig.HTTPAll.ForceLnRequest) {
|
||||
origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds)
|
||||
if origin != nil {
|
||||
// 强制变更原来访问的域名
|
||||
requestHost = this.ReqHost
|
||||
}
|
||||
|
||||
// 回源Header中去除If-None-Match和If-Modified-Since
|
||||
if !this.cacheRef.EnableIfNoneMatch {
|
||||
this.DeleteHeader("If-None-Match")
|
||||
}
|
||||
if !this.cacheRef.EnableIfModifiedSince {
|
||||
this.DeleteHeader("If-Modified-Since")
|
||||
if this.cacheRef != nil {
|
||||
// 回源Header中去除If-None-Match和If-Modified-Since
|
||||
if !this.cacheRef.EnableIfNoneMatch {
|
||||
this.DeleteHeader("If-None-Match")
|
||||
}
|
||||
if !this.cacheRef.EnableIfModifiedSince {
|
||||
this.DeleteHeader("If-Modified-Since")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,14 +115,20 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
requestHostHasVariables = origin.RequestHostHasVariables()
|
||||
}
|
||||
|
||||
// 处理OSS
|
||||
var isHTTPOrigin = origin.OSS == nil
|
||||
|
||||
// 处理Scheme
|
||||
if origin.Addr == nil {
|
||||
if isHTTPOrigin && origin.Addr == nil {
|
||||
err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
|
||||
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true)
|
||||
return
|
||||
}
|
||||
this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme()
|
||||
|
||||
if isHTTPOrigin {
|
||||
this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme()
|
||||
}
|
||||
|
||||
// StripPrefix
|
||||
if len(stripPrefix) > 0 {
|
||||
@@ -159,63 +165,66 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.uri = utils.CleanPath(this.uri)
|
||||
}
|
||||
|
||||
// 获取源站地址
|
||||
var originAddr = origin.Addr.PickAddress()
|
||||
if origin.Addr.HostHasVariables() {
|
||||
originAddr = this.Format(originAddr)
|
||||
}
|
||||
|
||||
// 端口跟随
|
||||
if origin.FollowPort {
|
||||
var originHostIndex = strings.Index(originAddr, ":")
|
||||
if originHostIndex < 0 {
|
||||
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
|
||||
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
|
||||
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
|
||||
return
|
||||
var originAddr = ""
|
||||
if isHTTPOrigin {
|
||||
// 获取源站地址
|
||||
originAddr = origin.Addr.PickAddress()
|
||||
if origin.Addr.HostHasVariables() {
|
||||
originAddr = this.Format(originAddr)
|
||||
}
|
||||
originAddr = originAddr[:originHostIndex+1] + types.String(this.requestServerPort())
|
||||
}
|
||||
this.originAddr = originAddr
|
||||
|
||||
// RequestHost
|
||||
if len(requestHost) > 0 {
|
||||
if requestHostHasVariables {
|
||||
this.RawReq.Host = this.Format(requestHost)
|
||||
// 端口跟随
|
||||
if origin.FollowPort {
|
||||
var originHostIndex = strings.Index(originAddr, ":")
|
||||
if originHostIndex < 0 {
|
||||
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
|
||||
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
|
||||
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
|
||||
return
|
||||
}
|
||||
originAddr = originAddr[:originHostIndex+1] + types.String(this.requestServerPort())
|
||||
}
|
||||
this.originAddr = originAddr
|
||||
|
||||
// RequestHost
|
||||
if len(requestHost) > 0 {
|
||||
if requestHostHasVariables {
|
||||
this.RawReq.Host = this.Format(requestHost)
|
||||
} else {
|
||||
this.RawReq.Host = requestHost
|
||||
}
|
||||
|
||||
// 是否移除端口
|
||||
if this.reverseProxy.RequestHostExcludingPort {
|
||||
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
|
||||
}
|
||||
|
||||
this.RawReq.URL.Host = this.RawReq.Host
|
||||
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
|
||||
// 源站主机名
|
||||
var hostname = originAddr
|
||||
if origin.Addr.Protocol.IsHTTPFamily() {
|
||||
hostname = strings.TrimSuffix(hostname, ":80")
|
||||
} else if origin.Addr.Protocol.IsHTTPSFamily() {
|
||||
hostname = strings.TrimSuffix(hostname, ":443")
|
||||
}
|
||||
|
||||
this.RawReq.Host = hostname
|
||||
|
||||
// 是否移除端口
|
||||
if this.reverseProxy.RequestHostExcludingPort {
|
||||
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
|
||||
}
|
||||
|
||||
this.RawReq.URL.Host = this.RawReq.Host
|
||||
} else {
|
||||
this.RawReq.Host = requestHost
|
||||
}
|
||||
this.RawReq.URL.Host = this.ReqHost
|
||||
|
||||
// 是否移除端口
|
||||
if this.reverseProxy.RequestHostExcludingPort {
|
||||
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
|
||||
}
|
||||
|
||||
this.RawReq.URL.Host = this.RawReq.Host
|
||||
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
|
||||
// 源站主机名
|
||||
var hostname = originAddr
|
||||
if origin.Addr.Protocol.IsHTTPFamily() {
|
||||
hostname = strings.TrimSuffix(hostname, ":80")
|
||||
} else if origin.Addr.Protocol.IsHTTPSFamily() {
|
||||
hostname = strings.TrimSuffix(hostname, ":443")
|
||||
}
|
||||
|
||||
this.RawReq.Host = hostname
|
||||
|
||||
// 是否移除端口
|
||||
if this.reverseProxy.RequestHostExcludingPort {
|
||||
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
|
||||
}
|
||||
|
||||
this.RawReq.URL.Host = this.RawReq.Host
|
||||
} else {
|
||||
this.RawReq.URL.Host = this.ReqHost
|
||||
|
||||
// 是否移除端口
|
||||
if this.reverseProxy.RequestHostExcludingPort {
|
||||
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
|
||||
this.RawReq.URL.Host = utils.ParseAddrHost(this.RawReq.URL.Host)
|
||||
// 是否移除端口
|
||||
if this.reverseProxy.RequestHostExcludingPort {
|
||||
this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host)
|
||||
this.RawReq.URL.Host = utils.ParseAddrHost(this.RawReq.URL.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,60 +250,104 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
}
|
||||
|
||||
// 判断是否为Websocket请求
|
||||
if this.RawReq.Header.Get("Upgrade") == "websocket" {
|
||||
if isHTTPOrigin && this.RawReq.Header.Get("Upgrade") == "websocket" {
|
||||
shouldRetry = this.doWebsocket(requestHost, isLastRetry)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取请求客户端
|
||||
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
|
||||
if err != nil {
|
||||
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
|
||||
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
|
||||
var resp *http.Response
|
||||
var respBodyIsClosed bool
|
||||
var requestErr error
|
||||
var requestErrCode string
|
||||
if isHTTPOrigin { // 普通HTTP(S)源站
|
||||
// 修复空User-Agent问题
|
||||
_, existsUserAgent := this.RawReq.Header["User-Agent"]
|
||||
if !existsUserAgent {
|
||||
this.RawReq.Header["User-Agent"] = []string{""}
|
||||
}
|
||||
|
||||
// 获取请求客户端
|
||||
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
|
||||
if err != nil {
|
||||
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
|
||||
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试自动纠正源站地址中的scheme
|
||||
if this.RawReq.URL.Scheme == "http" && strings.HasSuffix(originAddr, ":443") {
|
||||
this.RawReq.URL.Scheme = "https"
|
||||
} else if this.RawReq.URL.Scheme == "https" && strings.HasSuffix(originAddr, ":80") {
|
||||
this.RawReq.URL.Scheme = "http"
|
||||
}
|
||||
|
||||
// 开始请求
|
||||
resp, requestErr = client.Do(this.RawReq)
|
||||
} else if origin.OSS != nil { // OSS源站
|
||||
var goNext bool
|
||||
resp, goNext, requestErrCode, _, requestErr = this.doOSSOrigin(origin)
|
||||
if requestErr == nil {
|
||||
if resp == nil || !goNext {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.writeCode(http.StatusBadGateway, "The type of origin site has not been supported", "设置的源站类型尚未支持")
|
||||
return
|
||||
}
|
||||
|
||||
// 开始请求
|
||||
resp, err := client.Do(this.RawReq)
|
||||
if err != nil {
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer func() {
|
||||
if !respBodyIsClosed {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if requestErr != nil {
|
||||
// 客户端取消请求,则不提示
|
||||
httpErr, ok := err.(*url.Error)
|
||||
httpErr, ok := requestErr.(*url.Error)
|
||||
if !ok {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+err.Error())
|
||||
if isHTTPOrigin {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
}
|
||||
|
||||
if len(requestErrCode) > 0 {
|
||||
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site (error code: "+requestErrCode+")", "源站读取失败(错误代号:"+requestErrCode+")", true)
|
||||
} else {
|
||||
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 {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
if isHTTPOrigin {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
}
|
||||
|
||||
// 是否需要重试
|
||||
if (originId > 0 || (lnNodeId > 0 && hasMultipleLnNodes)) && !isLastRetry {
|
||||
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: "+err.Error())
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if httpErr.Timeout() {
|
||||
this.write50x(err, http.StatusGatewayTimeout, "Read origin site timeout", "源站读取超时", true)
|
||||
this.write50x(requestErr, http.StatusGatewayTimeout, "Read origin site timeout", "源站读取超时", true)
|
||||
} else if httpErr.Temporary() {
|
||||
this.write50x(err, http.StatusServiceUnavailable, "Origin site unavailable now", "源站当前不可用", true)
|
||||
this.write50x(requestErr, http.StatusServiceUnavailable, "Origin site unavailable now", "源站当前不可用", true)
|
||||
} else {
|
||||
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
}
|
||||
if httpErr.Err != io.EOF {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
|
||||
}
|
||||
} else {
|
||||
// 是否为客户端方面的错误
|
||||
@@ -314,22 +367,9 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
}
|
||||
|
||||
if !isClientError {
|
||||
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
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.")
|
||||
if resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -337,7 +377,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.originStatus = int32(resp.StatusCode)
|
||||
|
||||
// 恢复源站状态
|
||||
if !origin.IsOk {
|
||||
if !origin.IsOk && isHTTPOrigin {
|
||||
SharedOriginStateManager.Success(origin, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
@@ -346,23 +386,24 @@ 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
|
||||
}
|
||||
|
||||
// Page optimization
|
||||
if this.web.Optimization != nil && resp.Body != nil && this.cacheRef != nil /** must under cache **/ {
|
||||
err := this.web.Optimization.FilterResponse(resp)
|
||||
if err != nil {
|
||||
this.write50x(err, http.StatusBadGateway, "Page Optimization: Fail to read content from origin", "内容优化:从源站读取内容失败", false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置Charset
|
||||
// TODO 这里应该可以设置文本类型的列表,以及是否强制覆盖所有文本类型的字符集
|
||||
if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
|
||||
@@ -409,10 +450,22 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
|
||||
// 响应Header
|
||||
this.writer.AddHeaders(resp.Header)
|
||||
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||
|
||||
// 是否需要刷新
|
||||
var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
|
||||
var shouldAutoFlush = this.reverseProxy.AutoFlush || (resp.Header != nil && strings.Contains(resp.Header.Get("Content-Type"), "stream"))
|
||||
|
||||
// 设置当前连接为Persistence
|
||||
if shouldAutoFlush && this.nodeConfig != nil && this.nodeConfig.HasConnTimeoutSettings() {
|
||||
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if requestConn == nil {
|
||||
return
|
||||
}
|
||||
requestClientConn, ok := requestConn.(ClientConnInterface)
|
||||
if ok {
|
||||
requestClientConn.SetIsPersistent(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 准备
|
||||
var delayHeaders = this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)
|
||||
@@ -429,6 +482,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
|
||||
@@ -437,6 +491,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
// 输出到客户端
|
||||
var pool = this.bytePool(resp.ContentLength)
|
||||
var buf = pool.Get()
|
||||
var err error
|
||||
if shouldAutoFlush {
|
||||
for {
|
||||
n, readErr := resp.Body.Read(buf)
|
||||
@@ -453,11 +508,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())
|
||||
|
||||
@@ -30,10 +30,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)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
|
||||
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
|
||||
} else {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
|
||||
http.Redirect(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 {
|
||||
@@ -217,7 +230,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
// 支持 If-None-Match
|
||||
if this.requestHeader("If-None-Match") == eTag {
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.writer.WriteHeader(http.StatusNotModified)
|
||||
return true
|
||||
}
|
||||
@@ -225,7 +238,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
// 支持 If-Modified-Since
|
||||
if this.requestHeader("If-Modified-Since") == modifiedTime {
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||
this.writer.WriteHeader(http.StatusNotModified)
|
||||
return true
|
||||
}
|
||||
@@ -253,14 +266,14 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
var contentRange = this.RawReq.Header.Get("Range")
|
||||
if len(contentRange) > 0 {
|
||||
if fileSize == 0 {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
|
||||
set, ok := httpRequestParseRangeHeader(contentRange)
|
||||
if !ok {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -269,7 +282,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
for k, r := range ranges {
|
||||
r2, ok := r.Convert(fileSize)
|
||||
if !ok {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -290,7 +303,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
}
|
||||
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
|
||||
// 在Range请求中不能缓存
|
||||
if len(ranges) > 0 {
|
||||
@@ -325,7 +338,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
return true
|
||||
}
|
||||
if !ok {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
@@ -377,7 +390,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
return true
|
||||
}
|
||||
if !ok {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -5,21 +5,22 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 调用临时关闭页面
|
||||
func (this *HTTPRequest) doShutdown() {
|
||||
shutdown := this.web.Shutdown
|
||||
var shutdown = this.web.Shutdown
|
||||
if shutdown == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(shutdown.BodyType) == 0 || shutdown.BodyType == shared.BodyTypeURL {
|
||||
// URL
|
||||
if urlPrefixRegexp.MatchString(shutdown.URL) {
|
||||
if urlSchemeRegexp.MatchString(shutdown.URL) {
|
||||
this.doURL(http.MethodGet, shutdown.URL, "", shutdown.Status, true)
|
||||
return
|
||||
}
|
||||
@@ -28,44 +29,46 @@ func (this *HTTPRequest) doShutdown() {
|
||||
if len(shutdown.URL) == 0 {
|
||||
// 自定义响应Headers
|
||||
if shutdown.Status > 0 {
|
||||
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||
this.writer.WriteHeader(shutdown.Status)
|
||||
} else {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
_, err := this.writer.WriteString("The site have been shutdown.")
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
|
||||
_, _ = this.writer.WriteString("The site have been shutdown.")
|
||||
return
|
||||
}
|
||||
|
||||
// 从本地文件中读取
|
||||
file := Tea.Root + Tea.DS + shutdown.URL
|
||||
var realpath = path.Clean(shutdown.URL)
|
||||
if !strings.HasPrefix(realpath, "/pages/") && !strings.HasPrefix(realpath, "pages/") { // only files under "/pages/" can be used
|
||||
var msg = "404 page not found: '" + shutdown.URL + "'"
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
var file = Tea.Root + Tea.DS + shutdown.URL
|
||||
fp, err := os.Open(file)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
msg := "404 page not found: '" + shutdown.URL + "'"
|
||||
|
||||
var msg = "404 page not found: '" + shutdown.URL + "'"
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, err = this.writer.Write([]byte(msg))
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
// 自定义响应Headers
|
||||
if shutdown.Status > 0 {
|
||||
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||
this.writer.WriteHeader(shutdown.Status)
|
||||
} else {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
buf := utils.BytePool1k.Get()
|
||||
var buf = utils.BytePool1k.Get()
|
||||
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
|
||||
return []byte(this.Format(string(p)))
|
||||
})
|
||||
@@ -77,18 +80,13 @@ func (this *HTTPRequest) doShutdown() {
|
||||
} else {
|
||||
this.writer.SetOk()
|
||||
}
|
||||
|
||||
err = fp.Close()
|
||||
if err != nil {
|
||||
remotelogs.Warn("HTTP_REQUEST_SHUTDOWN", "close file failed: "+err.Error())
|
||||
}
|
||||
} else if shutdown.BodyType == shared.BodyTypeHTML {
|
||||
// 自定义响应Headers
|
||||
if shutdown.Status > 0 {
|
||||
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||
this.writer.WriteHeader(shutdown.Status)
|
||||
} else {
|
||||
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||
this.writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ func (this *HTTPRequest) doTrafficLimit() {
|
||||
this.tags = append(this.tags, "bandwidth")
|
||||
|
||||
var statusCode = 509
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
|
||||
this.writer.WriteHeader(statusCode)
|
||||
if len(config.NoticePageBody) != 0 {
|
||||
|
||||
@@ -44,9 +44,9 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
|
||||
|
||||
// Header
|
||||
if statusCode <= 0 {
|
||||
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||
} else {
|
||||
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), statusCode)
|
||||
}
|
||||
|
||||
if supportVariables {
|
||||
@@ -67,8 +67,8 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
|
||||
}
|
||||
|
||||
// 输出内容
|
||||
pool := this.bytePool(resp.ContentLength)
|
||||
buf := pool.Get()
|
||||
var pool = this.bytePool(resp.ContentLength)
|
||||
var buf = pool.Get()
|
||||
if supportVariables {
|
||||
_, err = utils.CopyWithFilter(this.writer, resp.Body, buf, func(p []byte) []byte {
|
||||
return []byte(this.Format(string(p)))
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func (this *HTTPRequest) doCheckUserAgent() (shouldStop bool) {
|
||||
if this.web.UserAgent == nil {
|
||||
if this.web.UserAgent == nil || !this.web.UserAgent.IsOn {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -22,6 +23,9 @@ var spiderRegexp = regexp.MustCompile(`(?i)(python|pycurl|http-client|httpclient
|
||||
// 内容范围正则,其中的每个括号里的内容都在被引用,不能轻易修改
|
||||
var contentRangeRegexp = regexp.MustCompile(`^bytes (\d+)-(\d+)/(\d+|\*)`)
|
||||
|
||||
// URL协议前缀
|
||||
var urlSchemeRegexp = regexp.MustCompile("^(?i)(http|https|ftp)://")
|
||||
|
||||
// 分解Range
|
||||
func httpRequestParseRangeHeader(rangeValue string) (result []rangeutils.Range, ok bool) {
|
||||
// 参考RFC:https://tools.ietf.org/html/rfc7233
|
||||
@@ -222,3 +226,16 @@ func httpRedirect(writer http.ResponseWriter, req *http.Request, url string, cod
|
||||
|
||||
http.Redirect(writer, req, url, code)
|
||||
}
|
||||
|
||||
// 分析URL中的Host部分
|
||||
func httpParseHost(urlString string) (host string, err error) {
|
||||
if !urlSchemeRegexp.MatchString(urlString) {
|
||||
urlString = "https://" + urlString
|
||||
}
|
||||
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil && u != nil {
|
||||
return "", err
|
||||
}
|
||||
return u.Host, nil
|
||||
}
|
||||
|
||||
@@ -145,6 +145,23 @@ func TestHTTPRequest_httpRequestNextId_Concurrent(t *testing.T) {
|
||||
a.IsTrue(countDuplicated == 0)
|
||||
}
|
||||
|
||||
func TestHTTPParseURL(t *testing.T) {
|
||||
for _, s := range []string{
|
||||
"",
|
||||
"null",
|
||||
"example.com",
|
||||
"https://example.com",
|
||||
"https://example.com/hello",
|
||||
} {
|
||||
host, err := httpParseHost(s)
|
||||
if err == nil {
|
||||
t.Log(s, "=>", host)
|
||||
} else {
|
||||
t.Log(s, "=>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHTTPRequest_httpRequestNextId(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 调用WAF
|
||||
@@ -171,13 +171,28 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
var currentURL = this.URL()
|
||||
if regionConfig.MatchCountryURL(currentURL) {
|
||||
// 检查国家/地区级别封禁
|
||||
var countryId = result.CountryId()
|
||||
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
|
||||
if !regionConfig.IsAllowedCountry(result.CountryId(), result.ProvinceId()) {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
this.writer.Flush()
|
||||
this.writer.Close()
|
||||
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(formattedHTML)))
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.writer.Write([]byte(formattedHTML))
|
||||
} else {
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
}
|
||||
|
||||
// 延时返回,避免攻击
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// 停止日志
|
||||
if !logDenying {
|
||||
@@ -192,13 +207,28 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
|
||||
if regionConfig.MatchProvinceURL(currentURL) {
|
||||
// 检查省份封禁
|
||||
var provinceId = result.ProvinceId()
|
||||
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
|
||||
if !regionConfig.IsAllowedProvince(result.CountryId(), result.ProvinceId()) {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
this.writer.Flush()
|
||||
this.writer.Close()
|
||||
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(formattedHTML)))
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.writer.Write([]byte(formattedHTML))
|
||||
} else {
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
}
|
||||
|
||||
// 延时返回,避免攻击
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// 停止日志
|
||||
if !logDenying {
|
||||
@@ -427,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
|
||||
|
||||
@@ -137,7 +137,7 @@ func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shou
|
||||
return
|
||||
}
|
||||
|
||||
this.processResponseHeaders(resp.Header, resp.StatusCode)
|
||||
this.ProcessResponseHeaders(resp.Header, resp.StatusCode)
|
||||
this.writer.statusCode = resp.StatusCode
|
||||
|
||||
// 将响应写回客户端
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user