Compare commits
65 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 |
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,7 +49,7 @@ function build() {
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
||||
cp "$ROOT"/configs/api_node.template.yaml "$DIST"/configs
|
||||
cp "$ROOT"/configs/cluster.template.yaml "$DIST"/configs
|
||||
cp -R "$ROOT"/www "$DIST"/
|
||||
cp -R "$ROOT"/pages "$DIST"/
|
||||
|
||||
2
build/configs/.gitignore
vendored
2
build/configs/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node.json
|
||||
api.yaml
|
||||
api_node.yaml
|
||||
cluster.yaml
|
||||
api_cluster.yaml
|
||||
*.cache
|
||||
@@ -1,2 +1,2 @@
|
||||
* `api.template.yaml` - API相关配置模板
|
||||
* `api_node.template.yaml` - API相关配置模板
|
||||
* `cluster.template.yaml` - 通过集群自动接入节点模板
|
||||
@@ -1,4 +0,0 @@
|
||||
rpc:
|
||||
endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
3
build/configs/api_node.template.yaml
Normal file
3
build/configs/api_node.template.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
rpc.endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
@@ -5,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()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -87,8 +89,8 @@ func (this *AppCmd) Print() {
|
||||
fmt.Println("")
|
||||
fmt.Println("Options:")
|
||||
|
||||
spaces := 20
|
||||
max := 40
|
||||
var spaces = 20
|
||||
var max = 40
|
||||
for _, option := range this.options {
|
||||
l := len(option.Code)
|
||||
if l < max && l > spaces {
|
||||
@@ -126,9 +128,12 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
var mainArg = args[0]
|
||||
this.callDirective(mainArg + ":before")
|
||||
|
||||
switch mainArg {
|
||||
case "-v", "version", "-version", "--version":
|
||||
this.runVersion()
|
||||
return
|
||||
@@ -151,19 +156,19 @@ func (this *AppCmd) Run(main func()) {
|
||||
|
||||
// 查找指令
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == args[0] {
|
||||
if directive.Arg == mainArg {
|
||||
directive.Callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("unknown command '" + args[0] + "'")
|
||||
fmt.Println("unknown command '" + mainArg + "'")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
@@ -191,7 +196,7 @@ func (this *AppCmd) runStart() {
|
||||
|
||||
_ = os.Setenv("EdgeBackground", "on")
|
||||
|
||||
cmd := exec.Command(os.Args[0])
|
||||
var cmd = exec.Command(this.exe())
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Foreground: false,
|
||||
Setsid: true,
|
||||
@@ -203,6 +208,9 @@ func (this *AppCmd) runStart() {
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
@@ -277,3 +285,69 @@ func (this *AppCmd) ParseOptions(args []string) map[string][]string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *AppCmd) exe() string {
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
exe = os.Args[0]
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
func (this *AppCmd) createSymLinks() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorList = []string{}
|
||||
|
||||
// bin
|
||||
{
|
||||
var target = "/usr/bin/" + teaconst.ProcessName
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != exe {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(exe, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log
|
||||
{
|
||||
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
|
||||
var target = "/var/log/" + teaconst.ProcessName + ".log"
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != realPath {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(realPath, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
return errors.New(strings.Join(errorList, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AppCmd) callDirective(code string) {
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == code {
|
||||
if directive.Callback != nil {
|
||||
directive.Callback()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -83,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)
|
||||
@@ -125,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())
|
||||
@@ -146,7 +147,7 @@ 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)
|
||||
}
|
||||
|
||||
// 常用语句
|
||||
@@ -539,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)
|
||||
}
|
||||
|
||||
// 初始化
|
||||
@@ -661,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
|
||||
|
||||
@@ -47,6 +47,10 @@ func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -69,6 +73,9 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
@@ -94,6 +101,9 @@ func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"golang.org/x/sys/unix"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
@@ -148,8 +150,7 @@ func (this *Manager) FindPolicy(policyId int64) *serverconfigs.HTTPCachePolicy {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
p, _ := this.policyMap[policyId]
|
||||
return p
|
||||
return this.policyMap[policyId]
|
||||
}
|
||||
|
||||
// FindStorageWithPolicy 根据策略ID查找存储
|
||||
@@ -157,8 +158,7 @@ func (this *Manager) FindStorageWithPolicy(policyId int64) StorageInterface {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
storage, _ := this.storageMap[policyId]
|
||||
return storage
|
||||
return this.storageMap[policyId]
|
||||
}
|
||||
|
||||
// NewStorageWithPolicy 根据策略获取存储对象
|
||||
@@ -178,8 +178,30 @@ func (this *Manager) TotalDiskSize() int64 {
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
var total = int64(0)
|
||||
var sidMap = map[string]bool{} // partition sid => bool
|
||||
for _, storage := range this.storageMap {
|
||||
total += storage.TotalDiskSize()
|
||||
// 这里不能直接用 storage.TotalDiskSize() 相加,因为多个缓存策略缓存目录可能处在同一个分区目录下
|
||||
fileStorage, ok := storage.(*FileStorage)
|
||||
if ok {
|
||||
var options = fileStorage.options // copy
|
||||
if options != nil {
|
||||
var dir = options.Dir // copy
|
||||
if len(dir) == 0 {
|
||||
continue
|
||||
}
|
||||
var stat = &unix.Statfs_t{}
|
||||
err := unix.Statfs(dir, stat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var sid = fmt.Sprintf("%d_%d", stat.Fsid.Val[0], stat.Fsid.Val[1])
|
||||
if sidMap[sid] {
|
||||
continue
|
||||
}
|
||||
sidMap[sid] = true
|
||||
total += int64(stat.Blocks-stat.Bfree) * int64(stat.Bsize) // we add extra int64() for darwin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total < 0 {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
modeSlow int32 = 1
|
||||
modeFast int32 = 2
|
||||
)
|
||||
|
||||
// MaxOpenFiles max open files manager
|
||||
type MaxOpenFiles struct {
|
||||
ticker *time.Ticker
|
||||
mode int32
|
||||
}
|
||||
|
||||
func NewMaxOpenFiles() *MaxOpenFiles {
|
||||
var f = &MaxOpenFiles{}
|
||||
f.ticker = time.NewTicker(1 * time.Second)
|
||||
f.init()
|
||||
return f
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) init() {
|
||||
goman.New(func() {
|
||||
for range this.ticker.C {
|
||||
// reset mode
|
||||
atomic.StoreInt32(&this.mode, modeFast)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Fast() {
|
||||
atomic.AddInt32(&this.mode, modeFast)
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) FinishAll() {
|
||||
this.Fast()
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Slow() {
|
||||
atomic.StoreInt32(&this.mode, modeSlow)
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Next() bool {
|
||||
return atomic.LoadInt32(&this.mode) != modeSlow
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMaxOpenFiles(t *testing.T) {
|
||||
var maxOpenFiles = caches.NewMaxOpenFiles()
|
||||
maxOpenFiles.Fast()
|
||||
t.Log("fast:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
time.Sleep(1*time.Second + 1*time.Millisecond)
|
||||
t.Log("slow 1 second:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log("slow 1 second:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Fast()
|
||||
t.Log("fast:", maxOpenFiles.Next())
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package caches
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -55,17 +57,12 @@ const (
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileTmpSuffix = ".tmp"
|
||||
MinDiskSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
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 文件缓存
|
||||
//
|
||||
// 文件结构:
|
||||
@@ -162,6 +159,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
IsFull: false,
|
||||
})
|
||||
}
|
||||
this.subDirs = subDirs
|
||||
this.checkDiskSpace()
|
||||
|
||||
err = newOptions.Init()
|
||||
@@ -262,7 +260,7 @@ func (this *FileStorage) Init() error {
|
||||
} else {
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return errors.New("[CACHE]can not create dir:" + err.Error())
|
||||
return fmt.Errorf("[CACHE]can not create dir: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +300,9 @@ func (this *FileStorage) Init() error {
|
||||
// 检查磁盘空间
|
||||
this.checkDiskSpace()
|
||||
|
||||
// clean *.trash directories
|
||||
this.cleanAllDeletedDirs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -424,7 +425,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
maxMemorySize = maxSize
|
||||
}
|
||||
var memoryStorage = this.memoryStorage
|
||||
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
if !fsutils.DiskIsExtremelyFast() && !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||
if err == nil {
|
||||
return writer, nil
|
||||
@@ -445,9 +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()
|
||||
@@ -456,9 +457,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if !isOk {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}
|
||||
}()
|
||||
@@ -490,7 +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
|
||||
}
|
||||
@@ -558,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 检查在各个系统中的稳定性
|
||||
@@ -572,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() {
|
||||
@@ -628,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
|
||||
}
|
||||
@@ -639,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, maxSize, func() {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}), nil
|
||||
}
|
||||
@@ -764,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) {
|
||||
@@ -772,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
|
||||
@@ -783,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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1044,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阈值时,多清理
|
||||
@@ -1239,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)
|
||||
@@ -1254,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
|
||||
}
|
||||
|
||||
@@ -1295,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 { // 控制数量
|
||||
@@ -1418,17 +1426,24 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
||||
|
||||
// 检查磁盘剩余空间
|
||||
func (this *FileStorage) checkDiskSpace() {
|
||||
if this.options != nil && len(this.options.Dir) > 0 {
|
||||
stat, err := fsutils.Stat(this.options.Dir)
|
||||
var minFreeSize = DefaultMinDiskFreeSpace
|
||||
|
||||
var options = this.options // copy
|
||||
if options != nil && options.MinFreeSize != nil && options.MinFreeSize.Bytes() > 0 {
|
||||
minFreeSize = uint64(options.MinFreeSize.Bytes())
|
||||
}
|
||||
|
||||
if options != nil && len(options.Dir) > 0 {
|
||||
stat, err := fsutils.Stat(options.Dir)
|
||||
if err == nil {
|
||||
this.mainDiskIsFull = stat.FreeSize() < MinDiskSpace
|
||||
this.mainDiskIsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
stat, err := fsutils.Stat(subDir.Path)
|
||||
if err == nil {
|
||||
subDir.IsFull = stat.FreeSize() < MinDiskSpace
|
||||
subDir.IsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,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"},
|
||||
|
||||
@@ -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"
|
||||
@@ -483,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, this.maxSize)
|
||||
// split LARGE data
|
||||
var l = len(data)
|
||||
if l > (2 << 20) {
|
||||
var offset = 0
|
||||
const bufferSize = 256 << 10
|
||||
for {
|
||||
var end = offset + bufferSize
|
||||
if end > l {
|
||||
end = l
|
||||
}
|
||||
n1, err1 := this.write(data[offset:end])
|
||||
n += n1
|
||||
if err1 != nil {
|
||||
return n, err1
|
||||
}
|
||||
if end >= l {
|
||||
return n, nil
|
||||
}
|
||||
offset = end
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
}
|
||||
return
|
||||
// write NORMAL size data
|
||||
return this.write(data)
|
||||
}
|
||||
|
||||
// WriteAt 在指定位置写入数据
|
||||
@@ -126,18 +138,24 @@ func (this *FileWriter) Close() error {
|
||||
|
||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
_ = os.Remove(path)
|
||||
return err
|
||||
}
|
||||
err = this.WriteBodyLength(this.bodySize)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
_ = os.Remove(path)
|
||||
return err
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
err = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
} else if strings.HasSuffix(path, FileTmpSuffix) {
|
||||
@@ -156,7 +174,9 @@ func (this *FileWriter) Discard() error {
|
||||
this.endFunc()
|
||||
})
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
err := os.Remove(this.rawWriter.Name())
|
||||
return err
|
||||
@@ -182,3 +202,23 @@ func (this *FileWriter) Key() string {
|
||||
func (this *FileWriter) ItemType() ItemType {
|
||||
return ItemTypeFile
|
||||
}
|
||||
|
||||
func (this *FileWriter) write(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.bodySize += int64(n)
|
||||
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
|
||||
if this.storage != nil {
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package caches
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"os"
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.2.3"
|
||||
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,12 @@ 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)
|
||||
|
||||
@@ -9,8 +9,8 @@ func TestOn(t *testing.T) {
|
||||
type User struct {
|
||||
name string
|
||||
}
|
||||
var u = &User{}
|
||||
var u2 = &User{}
|
||||
var u = &User{name: "lily"}
|
||||
var u2 = &User{name: "lucy"}
|
||||
|
||||
events.On("hello", func() {
|
||||
t.Log("world")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -92,7 +93,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
||||
// 对比配置
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return errors.New("encode config to json failed: " + err.Error())
|
||||
return fmt.Errorf("encode config to json failed: %w", err)
|
||||
}
|
||||
if !allowIPListChanged && bytes.Equal(this.lastConfig, configJSON) {
|
||||
return nil
|
||||
@@ -188,7 +189,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
for _, filter := range nftablesFilters {
|
||||
chain, oldRules, err := this.getRules(filter)
|
||||
if err != nil {
|
||||
return errors.New("get old rules failed: " + err.Error())
|
||||
return fmt.Errorf("get old rules failed: %w", err)
|
||||
}
|
||||
|
||||
var protocol = filter.protocol()
|
||||
@@ -273,7 +274,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
// 先清空所有相关规则
|
||||
err = this.removeOldTCPRules(chain, oldRules)
|
||||
if err != nil {
|
||||
return errors.New("delete old rules failed: " + err.Error())
|
||||
return fmt.Errorf("delete old rules failed: %w", err)
|
||||
}
|
||||
|
||||
// 添加新规则
|
||||
@@ -281,9 +282,9 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
if maxConnections > 0 {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +294,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,14 +306,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
} else {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,14 +326,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
} else {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,11 +499,11 @@ func (this *DDoSProtectionManager) getTable(filter *nftablesTableDefinition) (*n
|
||||
func (this *DDoSProtectionManager) getRules(filter *nftablesTableDefinition) (*nftables.Chain, []*nftables.Rule, error) {
|
||||
table, err := this.getTable(filter)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("get table failed: " + err.Error())
|
||||
return nil, nil, fmt.Errorf("get table failed: %w", err)
|
||||
}
|
||||
chain, err := table.GetChain(nftablesChainName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("get chain failed: " + err.Error())
|
||||
return nil, nil, fmt.Errorf("get chain failed: %w", err)
|
||||
}
|
||||
rules, err := chain.GetRules()
|
||||
return chain, rules, err
|
||||
@@ -538,7 +539,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
// 不存在则删除
|
||||
err = set.DeleteIPElement(ip)
|
||||
if err != nil {
|
||||
return errors.New("delete ip element '" + ip + "' failed: " + err.Error())
|
||||
return fmt.Errorf("delete ip element '%s' failed: %w", ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +557,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
// 不存在则添加
|
||||
err = set.AddIPElement(ip, nil, false)
|
||||
if err != nil {
|
||||
return errors.New("add ip '" + ip + "' failed: " + err.Error())
|
||||
return fmt.Errorf("add ip '%s' failed: %w", ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
@@ -194,7 +194,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("run command failed '" + cmd.String() + "': " + err.Error())
|
||||
return fmt.Errorf("run command failed '%s': %w", cmd.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -149,10 +150,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
table, err = this.conn.AddIPv6Table(tableDef.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("create table '" + tableDef.Name + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create table '%s' failed: %w", tableDef.Name, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get table '" + tableDef.Name + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get table '%s' failed: %w", tableDef.Name, err)
|
||||
}
|
||||
}
|
||||
if table == nil {
|
||||
@@ -166,10 +167,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
if nftables.IsNotFound(err) {
|
||||
chain, err = table.AddAcceptChain(chainName)
|
||||
if err != nil {
|
||||
return errors.New("create chain '" + chainName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create chain '%s' failed: %w", chainName, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get chain '" + chainName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get chain '%s' failed: %w", chainName, err)
|
||||
}
|
||||
}
|
||||
if chain == nil {
|
||||
@@ -184,7 +185,7 @@ func (this *NFTablesFirewall) init() error {
|
||||
_, err = chain.AddAcceptInterfaceRule("lo", loRuleName)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("add 'lo' rule failed: " + err.Error())
|
||||
return fmt.Errorf("add 'lo' rule failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,10 +208,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
HasTimeout: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("create set '" + setName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create set '%s' failed: %w", setName, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get set '" + setName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get set '%s' failed: %w", setName, err)
|
||||
}
|
||||
}
|
||||
if set == nil {
|
||||
@@ -259,10 +260,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("add rule failed: " + err.Error())
|
||||
return fmt.Errorf("add rule failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get rule failed: " + err.Error())
|
||||
return fmt.Errorf("get rule failed: %w", err)
|
||||
}
|
||||
}
|
||||
if rule == nil {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -85,7 +85,7 @@ func (this *Installer) Install() error {
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if len(NftExePath()) > 0 {
|
||||
if len(NftExePath()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func (this *Installer) Install() error {
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ": " + cmd.Stderr())
|
||||
return fmt.Errorf("%w: %s", err, cmd.Stderr())
|
||||
}
|
||||
|
||||
remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("%w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
@@ -70,7 +71,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if err != nil {
|
||||
var output = cmd.Stderr()
|
||||
if !strings.Contains(output, "already exists") {
|
||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -88,7 +89,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if err != nil {
|
||||
var output = cmd.Stderr()
|
||||
if !strings.Contains(output, "already exists") {
|
||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -116,7 +117,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if strings.Contains(output, "NAME_CONFLICT") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +135,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if strings.Contains(output, "NAME_CONFLICT") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +149,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd reload: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +201,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +222,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +284,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
return nil
|
||||
}
|
||||
|
||||
var listName = ""
|
||||
var listName string
|
||||
var isIPv6 = strings.Contains(item.IpFrom, ":")
|
||||
|
||||
switch listType {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
@@ -128,7 +128,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
||||
if strings.Contains(output, "No chain/target/match") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New(err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("%w, output: %s", err, output)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -102,7 +102,7 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
|
||||
continue
|
||||
}
|
||||
|
||||
instances, _ := this.eventMap[action.EventLevel]
|
||||
var instances = this.eventMap[action.EventLevel]
|
||||
instances = append(instances, instance)
|
||||
this.eventMap[action.EventLevel] = instances
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
@@ -62,7 +61,7 @@ func (this *ScriptAction) runAction(action string, listType IPListType, item *pb
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("%w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -50,7 +50,11 @@ func (this *APIStream) Start() {
|
||||
}
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Warn("API_STREAM", err.Error())
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Debug("API_STREAM", err.Error())
|
||||
} else {
|
||||
remotelogs.Warn("API_STREAM", err.Error())
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -76,7 +80,7 @@ func (this *APIStream) loop() error {
|
||||
if this.isQuiting {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
return err
|
||||
}
|
||||
this.stream = nodeStream
|
||||
|
||||
@@ -92,7 +96,7 @@ func (this *APIStream) loop() error {
|
||||
remotelogs.Println("API_STREAM", "quit")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@@ -438,10 +442,10 @@ func (this *APIStream) handleChangeAPINode(message *pb.NodeStreamMessage) error
|
||||
return nil
|
||||
}
|
||||
|
||||
config.RPC.Endpoints = []string{messageData.Addr}
|
||||
config.RPCEndpoints = []string{messageData.Addr}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "save config file failed: "+err.Error())
|
||||
return nil
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
@@ -98,7 +98,7 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
this.lastErr = errors.New("read error: " + err.Error())
|
||||
this.lastErr = fmt.Errorf("read error: %w", err)
|
||||
} else {
|
||||
this.lastErr = nil
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
this.lastErr = errors.New("write error: " + err.Error())
|
||||
this.lastErr = fmt.Errorf("write error: %w", err)
|
||||
} else {
|
||||
this.lastErr = nil
|
||||
}
|
||||
@@ -188,6 +188,8 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
var before = time.Now()
|
||||
n, err = this.rawConn.Write(b)
|
||||
if n > 0 {
|
||||
atomic.AddInt64(&this.totalSentBytes, int64(n))
|
||||
|
||||
// 统计当前服务带宽
|
||||
if this.serverId > 0 {
|
||||
// TODO 需要加入在serverId绑定之前的带宽
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,6 +26,8 @@ type BaseClientConn struct {
|
||||
isClosed bool
|
||||
|
||||
rawIP string
|
||||
|
||||
totalSentBytes int64
|
||||
}
|
||||
|
||||
func (this *BaseClientConn) IsClosed() bool {
|
||||
@@ -160,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,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"golang.org/x/net/http2"
|
||||
"net"
|
||||
@@ -20,6 +21,8 @@ import (
|
||||
// SharedHTTPClientPool HTTP客户端池单例
|
||||
var SharedHTTPClientPool = NewHTTPClientPool()
|
||||
|
||||
const httpClientProxyProtocolTag = "@ProxyProtocol@"
|
||||
|
||||
// HTTPClientPool 客户端池
|
||||
type HTTPClientPool struct {
|
||||
clientsMap map[string]*HTTPClient // backend key => client
|
||||
@@ -54,6 +57,14 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
}
|
||||
|
||||
var key = origin.UniqueKey() + "@" + originAddr
|
||||
|
||||
// if we are under available ProxyProtocol, we add client ip to key to make every client unique
|
||||
var isProxyProtocol = false
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn {
|
||||
key += httpClientProxyProtocolTag + req.requestRemoteAddr(true)
|
||||
isProxyProtocol = true
|
||||
}
|
||||
|
||||
var isLnRequest = origin.Id == 0
|
||||
|
||||
this.locker.RLock()
|
||||
@@ -102,8 +113,9 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
idleConns = numberCPU * 16
|
||||
}
|
||||
|
||||
// 可以判断为Ln节点请求
|
||||
if isLnRequest {
|
||||
if isProxyProtocol { // ProxyProtocol无需保持太多空闲连接
|
||||
idleConns = 3
|
||||
} else if isLnRequest { // 可以判断为Ln节点请求
|
||||
maxConnections *= 8
|
||||
idleConns *= 8
|
||||
idleTimeout *= 4
|
||||
@@ -146,7 +158,7 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
return NewOriginConn(conn), nil
|
||||
},
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: idleConns,
|
||||
@@ -195,22 +207,45 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
// 清理不使用的Client
|
||||
func (this *HTTPClientPool) cleanClients() {
|
||||
for range this.cleanTicker.C {
|
||||
var nowTime = time.Now().Unix()
|
||||
var nowTime = fasttime.Now().Unix()
|
||||
|
||||
this.locker.Lock()
|
||||
var expiredKeys = []string{}
|
||||
var expiredClients = []*HTTPClient{}
|
||||
|
||||
// lookup expired clients
|
||||
this.locker.RLock()
|
||||
for k, client := range this.clientsMap {
|
||||
if client.AccessTime() < nowTime+86400 { // 超过 N 秒没有调用就关闭
|
||||
if client.AccessTime() < nowTime-86400 ||
|
||||
(strings.Contains(k, httpClientProxyProtocolTag) && client.AccessTime() < nowTime-3600) { // 超过 N 秒没有调用就关闭
|
||||
expiredKeys = append(expiredKeys, k)
|
||||
expiredClients = append(expiredClients, client)
|
||||
}
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
// remove expired keys
|
||||
if len(expiredKeys) > 0 {
|
||||
this.locker.Lock()
|
||||
for _, k := range expiredKeys {
|
||||
delete(this.clientsMap, k)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// close expired clients
|
||||
if len(expiredClients) > 0 {
|
||||
for _, client := range expiredClients {
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// 支持PROXY Protocol
|
||||
func (this *HTTPClientPool) handlePROXYProtocol(conn net.Conn, req *HTTPRequest, proxyProtocol *serverconfigs.ProxyProtocolConfig) error {
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
if proxyProtocol != nil &&
|
||||
proxyProtocol.IsOn &&
|
||||
(proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
var remoteAddr = req.requestRemoteAddr(true)
|
||||
var transportProtocol = proxyproto.TCPv4
|
||||
if strings.Contains(remoteAddr, ":") {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -16,7 +17,7 @@ func TestHTTPClientPool_Client(t *testing.T) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init(nil)
|
||||
err := origin.Init(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -43,7 +44,7 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init(nil)
|
||||
err := origin.Init(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func BenchmarkHTTPClientPool_Client(b *testing.B) {
|
||||
Version: 2,
|
||||
Addr: &serverconfigs.NetworkAddressConfig{Host: "127.0.0.1", PortRange: "1234"},
|
||||
}
|
||||
err := origin.Init(nil)
|
||||
err := origin.Init(context.Background())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -389,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
|
||||
|
||||
@@ -397,14 +412,17 @@ func (this *HTTPRequest) doEnd() {
|
||||
|
||||
if this.isCached {
|
||||
countCached = 1
|
||||
cachedBytes = this.writer.SentBodyBytes() + this.writer.SentHeaderBytes()
|
||||
cachedBytes = totalBytes
|
||||
}
|
||||
if this.isAttack {
|
||||
countAttacks = 1
|
||||
attackBytes = this.CalculateSize()
|
||||
if attackBytes < totalBytes {
|
||||
attackBytes = totalBytes
|
||||
}
|
||||
}
|
||||
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, this.writer.SentBodyBytes()+this.writer.SentHeaderBytes(), cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, totalBytes, cachedBytes, 1, countCached, countAttacks, attackBytes, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
|
||||
// 指标
|
||||
if metrics.SharedManager.HasHTTPMetrics() {
|
||||
|
||||
@@ -90,6 +90,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// 是否强制Range回源
|
||||
if this.cacheRef.AlwaysForwardRangeRequest && len(this.RawReq.Header.Get("Range")) > 0 {
|
||||
this.cacheRef = nil
|
||||
cacheBypassDescription = "BYPASS, forward range"
|
||||
return
|
||||
}
|
||||
|
||||
// 是否正在Purge
|
||||
var isPurging = this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey
|
||||
if isPurging {
|
||||
|
||||
@@ -111,7 +111,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
||||
|
||||
// 处理SCRIPT_FILENAME
|
||||
scriptPath := env.GetString("SCRIPT_FILENAME")
|
||||
if len(scriptPath) > 0 && (strings.Index(scriptPath, "/") < 0 && strings.Index(scriptPath, "\\") < 0) {
|
||||
if len(scriptPath) > 0 && !strings.Contains(scriptPath, "/") && !strings.Contains(scriptPath, "\\") {
|
||||
env["SCRIPT_FILENAME"] = env.GetString("DOCUMENT_ROOT") + Tea.DS + scriptPath
|
||||
}
|
||||
scriptFilename := filepath.Base(this.RawReq.URL.Path)
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
var fullURL = ""
|
||||
var fullURL string
|
||||
if u.BeforeHasQuery() {
|
||||
fullURL = this.URL()
|
||||
} else {
|
||||
@@ -194,7 +194,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
var containsPort = false
|
||||
var containsPort bool
|
||||
if u.PortsAll {
|
||||
containsPort = true
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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"
|
||||
@@ -35,9 +36,24 @@ func (this *HTTPRequest) doMismatch() {
|
||||
// 根据配置进行相应的处理
|
||||
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 {
|
||||
_, _ = this.writer.WriteString(globalServerConfig.HTTPAll.NodeIPPageHTML)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -55,13 +71,13 @@ func (this *HTTPRequest) doMismatch() {
|
||||
}
|
||||
|
||||
// 处理当前连接
|
||||
var httpAllConfig = 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)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultPageContentType = "text/html; charset=utf-8"
|
||||
|
||||
// 请求特殊页面
|
||||
func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
||||
if len(this.web.Pages) == 0 {
|
||||
@@ -58,6 +60,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
var realpath = path.Clean(page.URL)
|
||||
if !strings.HasPrefix(realpath, "/pages/") && !strings.HasPrefix(realpath, "pages/") { // only files under "/pages/" can be used
|
||||
var msg = "404 page not found: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
@@ -66,6 +69,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
fp, err := os.Open(file)
|
||||
if err != nil {
|
||||
var msg = "404 page not found: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
@@ -77,6 +81,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
var msg = "404 could not read page content: '" + page.URL + "'"
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
return true
|
||||
@@ -85,10 +90,12 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||
this.writer.Prepare(nil, stat.Size(), page.NewStatus, true)
|
||||
this.writer.WriteHeader(page.NewStatus)
|
||||
} else {
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
this.writer.Prepare(nil, stat.Size(), status, true)
|
||||
this.writer.WriteHeader(status)
|
||||
@@ -120,10 +127,12 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||
this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true)
|
||||
this.writer.WriteHeader(page.NewStatus)
|
||||
} else {
|
||||
this.writer.Header().Set("Content-Type", defaultPageContentType)
|
||||
this.ProcessResponseHeaders(this.writer.Header(), status)
|
||||
this.writer.Prepare(nil, int64(len(content)), status, true)
|
||||
this.writer.WriteHeader(status)
|
||||
|
||||
@@ -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
|
||||
@@ -258,6 +256,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var respBodyIsClosed bool
|
||||
var requestErr error
|
||||
var requestErrCode string
|
||||
if isHTTPOrigin { // 普通HTTP(S)源站
|
||||
@@ -296,6 +295,15 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.writeCode(http.StatusBadGateway, "The type of origin site has not been supported", "设置的源站类型尚未支持")
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer func() {
|
||||
if !respBodyIsClosed {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if requestErr != nil {
|
||||
// 客户端取消请求,则不提示
|
||||
httpErr, ok := requestErr.(*url.Error)
|
||||
@@ -324,10 +332,6 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
shouldRetry = true
|
||||
this.uri = oldURI // 恢复备份
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
if httpErr.Err != io.EOF {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error())
|
||||
}
|
||||
@@ -366,19 +370,6 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||
}
|
||||
}
|
||||
if resp != nil && resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 是否为1.1以下
|
||||
if isLowVersionHTTP && resp.ContentLength < 0 {
|
||||
this.writer.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = this.writer.WriteString("The content does not support " + this.RawReq.Proto + " request.")
|
||||
if resp.Body != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -395,20 +386,12 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
// WAF对出站进行检查
|
||||
if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn {
|
||||
if this.doWAFResponse(resp) {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊页面
|
||||
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error())
|
||||
}
|
||||
if this.doPage(resp.StatusCode) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -499,6 +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
|
||||
@@ -524,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())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -174,11 +174,19 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
if !regionConfig.IsAllowedCountry(result.CountryId(), result.ProvinceId()) {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
|
||||
var promptHTML string
|
||||
if len(regionConfig.CountryHTML) > 0 {
|
||||
promptHTML = regionConfig.CountryHTML
|
||||
} else if this.ReqServer != nil && this.ReqServer.HTTPFirewallPolicy != nil && len(this.ReqServer.HTTPFirewallPolicy.DenyCountryHTML) > 0 {
|
||||
promptHTML = this.ReqServer.HTTPFirewallPolicy.DenyCountryHTML
|
||||
}
|
||||
|
||||
if len(promptHTML) > 0 {
|
||||
var formattedHTML = this.Format(promptHTML)
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(regionConfig.CountryHTML)))
|
||||
this.writer.Header().Set("Content-Length", types.String(len(formattedHTML)))
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.writer.Write([]byte(regionConfig.CountryHTML))
|
||||
_, _ = this.writer.Write([]byte(formattedHTML))
|
||||
} else {
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
}
|
||||
@@ -202,11 +210,19 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
if !regionConfig.IsAllowedProvince(result.CountryId(), result.ProvinceId()) {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
|
||||
var promptHTML string
|
||||
if len(regionConfig.ProvinceHTML) > 0 {
|
||||
promptHTML = regionConfig.ProvinceHTML
|
||||
} else if this.ReqServer != nil && this.ReqServer.HTTPFirewallPolicy != nil && len(this.ReqServer.HTTPFirewallPolicy.DenyProvinceHTML) > 0 {
|
||||
promptHTML = this.ReqServer.HTTPFirewallPolicy.DenyProvinceHTML
|
||||
}
|
||||
|
||||
if len(promptHTML) > 0 {
|
||||
var formattedHTML = this.Format(promptHTML)
|
||||
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
this.writer.Header().Set("Content-Length", types.String(len(regionConfig.ProvinceHTML)))
|
||||
this.writer.Header().Set("Content-Length", types.String(len(formattedHTML)))
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.writer.Write([]byte(regionConfig.ProvinceHTML))
|
||||
_, _ = this.writer.Write([]byte(formattedHTML))
|
||||
} else {
|
||||
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
|
||||
}
|
||||
@@ -441,6 +457,14 @@ func (this *HTTPRequest) WAFFingerprint() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) WAFMaxRequestSize() int64 {
|
||||
var maxRequestSize = firewallconfigs.DefaultMaxRequestBodySize
|
||||
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.MaxRequestBodySize > 0 {
|
||||
maxRequestSize = this.ReqServer.HTTPFirewallPolicy.MaxRequestBodySize
|
||||
}
|
||||
return maxRequestSize
|
||||
}
|
||||
|
||||
// DisableAccessLog 在当前请求中不使用访问日志
|
||||
func (this *HTTPRequest) DisableAccessLog() {
|
||||
this.disableLog = true
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
@@ -24,7 +25,6 @@ import (
|
||||
_ "golang.org/x/image/webp"
|
||||
"image"
|
||||
"image/gif"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
@@ -324,11 +324,11 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
||||
|
||||
// 待写入尺寸
|
||||
var totalSize = size
|
||||
if totalSize < 0 && this.isPartial {
|
||||
if this.isPartial {
|
||||
var contentRange = resp.Header.Get("Content-Range")
|
||||
if len(contentRange) > 0 {
|
||||
_, partialTotalSize := httpRequestParseContentRangeHeader(contentRange)
|
||||
if partialTotalSize > 0 {
|
||||
if partialTotalSize > 0 && partialTotalSize > totalSize {
|
||||
totalSize = partialTotalSize
|
||||
}
|
||||
}
|
||||
@@ -362,10 +362,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
||||
// 写入Header
|
||||
var headerBuf = utils.SharedBufferPool.Get()
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" ||
|
||||
(this.isPartial && k == "Content-Range") {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
for _, v1 := range v {
|
||||
@@ -410,7 +407,9 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
||||
var filterReader = readers.NewFilterReaderCloser(resp.Body)
|
||||
this.cacheIsFinished = true
|
||||
var hasError = false
|
||||
filterReader.Add(func(p []byte, err error) error {
|
||||
filterReader.Add(func(p []byte, readErr error) error {
|
||||
// 这里不用处理readErr,因为只要把成功读取的部分写入缓存即可
|
||||
|
||||
if hasError {
|
||||
return nil
|
||||
}
|
||||
@@ -693,10 +692,7 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
||||
// 写入Header
|
||||
var headerBuffer = utils.SharedBufferPool.Get()
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" ||
|
||||
(this.isPartial && k == "Content-Range") {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
for _, v1 := range v {
|
||||
@@ -735,7 +731,6 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
||||
}
|
||||
|
||||
// compression writer
|
||||
var err error = nil
|
||||
compressionWriter, err := compressions.NewWriter(this.writer, compressionType, int(this.compressionConfig.Level))
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_WRITER", err.Error())
|
||||
@@ -869,7 +864,7 @@ func (this *HTTPWriter) SendFile(status int, path string) (int64, error) {
|
||||
|
||||
fp, err := os.OpenFile(path, os.O_RDONLY, 0444)
|
||||
if err != nil {
|
||||
return 0, errors.New("open file '" + path + "' failed: " + err.Error())
|
||||
return 0, fmt.Errorf("open file '%s' failed: %w", path, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
@@ -1039,9 +1034,7 @@ func (this *HTTPWriter) finishWebP() {
|
||||
if webpCacheWriter != nil {
|
||||
// 写入Header
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1063,7 +1056,9 @@ func (this *HTTPWriter) finishWebP() {
|
||||
if webpCacheWriter != nil {
|
||||
var teeWriter = writers.NewTeeWriterCloser(this.writer, webpCacheWriter)
|
||||
teeWriter.OnFail(func(err error) {
|
||||
_ = webpCacheWriter.Discard()
|
||||
if webpCacheWriter != nil {
|
||||
_ = webpCacheWriter.Discard()
|
||||
}
|
||||
webpCacheWriter = nil
|
||||
})
|
||||
this.writer = teeWriter
|
||||
@@ -1260,10 +1255,7 @@ func (this *HTTPWriter) finishRequest() {
|
||||
// 计算Header长度
|
||||
func (this *HTTPWriter) calculateHeaderLength() (result int) {
|
||||
for k, v := range this.Header() {
|
||||
if k == "Set-Cookie" ||
|
||||
k == "Strict-Transport-Security" ||
|
||||
k == "Alt-Svc" ||
|
||||
(this.isPartial && k == "Content-Range") {
|
||||
if this.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
for _, v1 := range v {
|
||||
@@ -1272,3 +1264,12 @@ func (this *HTTPWriter) calculateHeaderLength() (result int) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPWriter) shouldIgnoreHeader(name string) bool {
|
||||
switch name {
|
||||
case "Set-Cookie", "Strict-Transport-Security", "Alt-Svc", "Upgrade":
|
||||
return true
|
||||
default:
|
||||
return (this.isPartial && name == "Content-Range")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
||||
// 指纹信息
|
||||
var fingerprint = this.calculateFingerprint(clientInfo)
|
||||
if len(fingerprint) > 0 {
|
||||
if len(fingerprint) > 0 && clientInfo.Conn != nil {
|
||||
clientConn, ok := clientInfo.Conn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetFingerprint(fingerprint)
|
||||
@@ -61,7 +61,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
||||
// 指纹信息
|
||||
var fingerprint = this.calculateFingerprint(clientInfo)
|
||||
if len(fingerprint) > 0 {
|
||||
if len(fingerprint) > 0 && clientInfo.Conn != nil {
|
||||
clientConn, ok := clientInfo.Conn.(ClientConnInterface)
|
||||
if ok {
|
||||
clientConn.SetFingerprint(fingerprint)
|
||||
@@ -235,7 +235,7 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
|
||||
// 从Hello信息中获取服务名称
|
||||
func (this *BaseListener) helloServerName(clientInfo *tls.ClientHelloInfo) string {
|
||||
var serverName = clientInfo.ServerName
|
||||
if len(serverName) == 0 {
|
||||
if len(serverName) == 0 && clientInfo.Conn != nil {
|
||||
var localAddr = clientInfo.Conn.LocalAddr()
|
||||
if localAddr != nil {
|
||||
tcpAddr, ok := localAddr.(*net.TCPAddr)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -23,7 +24,7 @@ func TestBaseListener_FindServer(t *testing.T) {
|
||||
{Name: types.String(i) + ".hello.com"},
|
||||
},
|
||||
}
|
||||
_ = server.Init(nil)
|
||||
_ = server.Init(context.Background())
|
||||
listener.Group.Add(server)
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
|
||||
// 检查host是否为IP
|
||||
func (this *HTTPListener) isIP(host string) bool {
|
||||
// IPv6
|
||||
if strings.Index(host, "[") > -1 {
|
||||
if strings.Contains(host, "[") {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -141,7 +141,7 @@ func (this *ListenerManager) Start(nodeConfig *nodeconfigs.NodeConfig) error {
|
||||
var port = addr[portIndex+1:]
|
||||
var processName = this.findProcessNameWithPort(group.IsUDP(), port)
|
||||
if len(processName) > 0 {
|
||||
err = errors.New(err.Error() + " (the process using port: '" + processName + "')")
|
||||
err = fmt.Errorf("%w (the process using port: '%s')", err, processName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
@@ -34,7 +36,6 @@ import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -95,11 +96,11 @@ func (this *Node) Test() error {
|
||||
// 检查是否能连接API
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("test rpc failed: " + err.Error())
|
||||
return fmt.Errorf("test rpc failed: %w", err)
|
||||
}
|
||||
_, err = rpcClient.APINodeRPC.FindCurrentAPINodeVersion(rpcClient.Context(), &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
return errors.New("test rpc failed: " + err.Error())
|
||||
return fmt.Errorf("test rpc failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -141,9 +142,6 @@ func (this *Node) Start() {
|
||||
// 调整系统参数
|
||||
this.checkSystem()
|
||||
|
||||
// 检查硬盘类型
|
||||
this.checkDisk()
|
||||
|
||||
// 启动事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
@@ -197,7 +195,7 @@ func (this *Node) Start() {
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
teaconst.NodeIdString = types.String(teaconst.NodeId)
|
||||
err, serverErrors := nodeConfig.Init(nil)
|
||||
err, serverErrors := nodeConfig.Init(context.Background())
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "init node config failed: "+err.Error())
|
||||
return
|
||||
@@ -315,17 +313,17 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
apiConfigFile := Tea.ConfigFile("api.yaml")
|
||||
// 检查api_node.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile(configs.ConfigFileName)
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
clusterErr := this.checkClusterConfig()
|
||||
if clusterErr != nil {
|
||||
if os.IsNotExist(clusterErr) {
|
||||
return errors.New("can not find config file 'configs/api.yaml'")
|
||||
return fmt.Errorf("can not find config file 'configs/%s'", configs.ConfigFileName)
|
||||
}
|
||||
return errors.New("check cluster config failed: " + clusterErr.Error())
|
||||
return fmt.Errorf("check cluster config failed: %w", clusterErr)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
@@ -334,7 +332,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("create rpc client failed: " + err.Error())
|
||||
return fmt.Errorf("create rpc client failed: %w", err)
|
||||
}
|
||||
|
||||
// 获取同步任务
|
||||
@@ -346,7 +344,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
UseDataMap: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("read config from rpc failed: " + err.Error())
|
||||
return fmt.Errorf("read config from rpc failed: %w", err)
|
||||
}
|
||||
if !configResp.IsChanged {
|
||||
return nil
|
||||
@@ -374,7 +372,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
var nodeConfig = &nodeconfigs.NodeConfig{}
|
||||
err = json.Unmarshal(configJSON, nodeConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode config failed: " + err.Error())
|
||||
return fmt.Errorf("decode config failed: %w", err)
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
teaconst.NodeIdString = types.String(teaconst.NodeId)
|
||||
@@ -394,7 +392,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err, serverErrors := nodeConfig.Init(nil)
|
||||
err, serverErrors := nodeConfig.Init(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -525,21 +523,15 @@ func (this *Node) startSyncTimer() {
|
||||
|
||||
// 检查集群设置
|
||||
func (this *Node) checkClusterConfig() error {
|
||||
configFile := Tea.ConfigFile("cluster.yaml")
|
||||
data, err := os.ReadFile(configFile)
|
||||
config, err := configs.LoadClusterConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := &configs.ClusterConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: config.RPC,
|
||||
NodeId: config.ClusterId,
|
||||
Secret: config.Secret,
|
||||
RPCEndpoints: config.RPCEndpoints,
|
||||
RPCDisableUpdate: config.RPCDisableUpdate,
|
||||
NodeId: config.ClusterId,
|
||||
Secret: config.Secret,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -557,22 +549,17 @@ func (this *Node) checkClusterConfig() error {
|
||||
resp.Endpoints = []string{}
|
||||
}
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: resp.Endpoints,
|
||||
DisableUpdate: false,
|
||||
},
|
||||
NodeId: resp.UniqueId,
|
||||
Secret: resp.Secret,
|
||||
RPCEndpoints: resp.Endpoints,
|
||||
RPCDisableUpdate: false,
|
||||
NodeId: resp.UniqueId,
|
||||
Secret: resp.Secret,
|
||||
}
|
||||
remotelogs.Debug("NODE", "writing 'configs/api.yaml' ...")
|
||||
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
remotelogs.Debug("NODE", "writing 'configs/"+configs.ConfigFileName+"' ...")
|
||||
err = apiConfig.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Debug("NODE", "wrote 'configs/api.yaml' successfully")
|
||||
remotelogs.Debug("NODE", "wrote 'configs/"+configs.ConfigFileName+"' successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1037,7 +1024,7 @@ func (this *Node) reloadServer() {
|
||||
remotelogs.Debug("NODE", "reload "+types.String(countUpdatingServers)+" servers")
|
||||
}
|
||||
|
||||
err, serverErrors := newNodeConfig.Init(nil)
|
||||
err, serverErrors := newNodeConfig.Init(context.Background())
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "apply server config error: "+err.Error())
|
||||
return
|
||||
@@ -1054,6 +1041,9 @@ func (this *Node) reloadServer() {
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "apply server config error: "+err.Error())
|
||||
}
|
||||
|
||||
// notify event
|
||||
events.Notify(events.EventReloadSomeServers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,28 +1097,6 @@ func (this *Node) checkSystem() {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查硬盘
|
||||
func (this *Node) checkDisk() {
|
||||
if runtime.GOOS != "linux" {
|
||||
return
|
||||
}
|
||||
for n := 'a'; n <= 'z'; n++ {
|
||||
for _, path := range []string{
|
||||
"/sys/block/vd" + string(n) + "/queue/rotational",
|
||||
"/sys/block/sd" + string(n) + "/queue/rotational",
|
||||
} {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if string(data) == "0" {
|
||||
teaconst.DiskIsFast = true
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查API节点地址
|
||||
func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddressConfig) {
|
||||
var addrs = []string{}
|
||||
@@ -1156,7 +1124,7 @@ func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddres
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
var oldEndpoints = config.RPC.Endpoints
|
||||
var oldEndpoints = config.RPCEndpoints
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
@@ -1170,9 +1138,9 @@ func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddres
|
||||
go func(v int64) {
|
||||
// 测试新的API节点地址
|
||||
if rpcClient.TestEndpoints(addrs) {
|
||||
config.RPC.Endpoints = addrs
|
||||
config.RPCEndpoints = addrs
|
||||
} else {
|
||||
config.RPC.Endpoints = oldEndpoints
|
||||
config.RPCEndpoints = oldEndpoints
|
||||
this.lastAPINodeAddrs = nil // 恢复为空,以便于下次更新重试
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build arm64
|
||||
// +build arm64
|
||||
|
||||
package nodes
|
||||
|
||||
// 处理异常
|
||||
func (this *Node) handlePanic() {
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ func (this *NodeStatusExecutor) update() {
|
||||
status.BuildVersionCode = utils.VersionToLong(teaconst.Version)
|
||||
status.OS = runtime.GOOS
|
||||
status.Arch = runtime.GOARCH
|
||||
exe, _ := os.Executable()
|
||||
status.ExePath = exe
|
||||
status.ExePath, _ = os.Executable()
|
||||
status.ConfigVersion = sharedNodeConfig.Version
|
||||
status.IsActive = true
|
||||
status.ConnectionCount = sharedListenerManager.TotalActiveConnections()
|
||||
@@ -208,6 +207,8 @@ func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
|
||||
|
||||
// 更新硬盘
|
||||
func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
||||
status.DiskWritingSpeedMB = int(fsutils.DiskSpeedMB)
|
||||
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE_STATUS", err.Error())
|
||||
|
||||
@@ -4,11 +4,12 @@ package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
@@ -26,8 +27,8 @@ func (this *Node) loopTasks() error {
|
||||
var tr = trackers.Begin("CHECK_NODE_CONFIG_CHANGES")
|
||||
defer tr.End()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile("api.yaml")
|
||||
// 检查api_node.yaml是否存在
|
||||
var apiConfigFile = Tea.ConfigFile(configs.ConfigFileName)
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -35,7 +36,7 @@ func (this *Node) loopTasks() error {
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.New("create rpc client failed: " + err.Error())
|
||||
return fmt.Errorf("create rpc client failed: %w", err)
|
||||
}
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
|
||||
@@ -45,7 +46,7 @@ func (this *Node) loopTasks() error {
|
||||
if rpc.IsConnError(err) && !Tea.IsTesting() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("read node tasks failed: " + err.Error())
|
||||
return fmt.Errorf("read node tasks failed: %w", err)
|
||||
}
|
||||
for _, task := range tasksResp.NodeTasks {
|
||||
err := this.execTask(rpcClient, task)
|
||||
@@ -147,7 +148,7 @@ func (this *Node) execNodeLevelChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
|
||||
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
|
||||
if err != nil {
|
||||
return errors.New("decode level info failed: " + err.Error())
|
||||
return fmt.Errorf("decode level info failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +175,7 @@ func (this *Node) execDDoSProtectionChangedTask(rpcClient *rpc.RPCClient) error
|
||||
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
|
||||
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode DDoS protection config failed: " + err.Error())
|
||||
return fmt.Errorf("decode DDoS protection config failed: %w", err)
|
||||
}
|
||||
|
||||
if ddosProtectionConfig != nil && sharedNodeConfig != nil {
|
||||
@@ -202,13 +203,13 @@ func (this *Node) execGlobalServerConfigChangedTask(rpcClient *rpc.RPCClient) er
|
||||
var globalServerConfig = serverconfigs.NewGlobalServerConfig()
|
||||
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode global server config failed: " + err.Error())
|
||||
return fmt.Errorf("decode global server config failed: %w", err)
|
||||
}
|
||||
|
||||
if globalServerConfig != nil {
|
||||
err = globalServerConfig.Init()
|
||||
if err != nil {
|
||||
return errors.New("validate global server config failed: " + err.Error())
|
||||
return fmt.Errorf("validate global server config failed: %w", err)
|
||||
}
|
||||
if sharedNodeConfig != nil {
|
||||
sharedNodeConfig.GlobalServerConfig = globalServerConfig
|
||||
@@ -256,7 +257,7 @@ func (this *Node) execUpdatingServersTask(rpcClient *rpc.RPCClient) error {
|
||||
var serverConfigs = []*serverconfigs.ServerConfig{}
|
||||
err = json.Unmarshal(resp.ServersJSON, &serverConfigs)
|
||||
if err != nil {
|
||||
return errors.New("decode server configs failed: " + err.Error())
|
||||
return fmt.Errorf("decode server configs failed: %w", err)
|
||||
}
|
||||
|
||||
if resp.MaxId > this.lastUpdatingServerListId {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"io"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -22,29 +18,3 @@ func TestNode_Test(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestNode_Proto_Buffer(t *testing.T) {
|
||||
buff := proto.NewBuffer([]byte{})
|
||||
for i := 0; i < 10; i++ {
|
||||
err := buff.EncodeMessage(&pb.NodeStreamMessage{
|
||||
RequestId: int64(i),
|
||||
Code: "msg" + strconv.Itoa(i),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 11; i++ {
|
||||
msg := &pb.NodeStreamMessage{}
|
||||
err := buff.DecodeMessage(msg)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
t.Log(msg.Code, msg.RequestId)
|
||||
}
|
||||
}
|
||||
|
||||
102
internal/nodes/origin_conn.go
Normal file
102
internal/nodes/origin_conn.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const originConnCloseDelaySeconds = 3
|
||||
|
||||
var closingOriginConnMap = map[*OriginConn]zero.Zero{}
|
||||
var closingOriginConnLocker = &sync.RWMutex{}
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
var ticker = time.NewTicker(originConnCloseDelaySeconds * time.Second)
|
||||
for range ticker.C {
|
||||
CleanOriginConnsTask()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func CleanOriginConnsTask() {
|
||||
var closingConns = []*OriginConn{}
|
||||
|
||||
closingOriginConnLocker.RLock()
|
||||
for conn := range closingOriginConnMap {
|
||||
if conn.IsExpired() {
|
||||
closingConns = append(closingConns, conn)
|
||||
}
|
||||
}
|
||||
closingOriginConnLocker.RUnlock()
|
||||
|
||||
if len(closingConns) > 0 {
|
||||
for _, conn := range closingConns {
|
||||
_ = conn.ForceClose()
|
||||
closingOriginConnLocker.Lock()
|
||||
delete(closingOriginConnMap, conn)
|
||||
closingOriginConnLocker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OriginConn connection with origin site
|
||||
type OriginConn struct {
|
||||
net.Conn
|
||||
|
||||
lastReadOk bool
|
||||
lastReadAt int64
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
// NewOriginConn create new origin connection
|
||||
func NewOriginConn(rawConn net.Conn) net.Conn {
|
||||
return &OriginConn{Conn: rawConn}
|
||||
}
|
||||
|
||||
// Read implement Read() for net.Conn interface
|
||||
func (this *OriginConn) Read(b []byte) (n int, err error) {
|
||||
n, err = this.Conn.Read(b)
|
||||
this.lastReadOk = err == nil
|
||||
if this.lastReadOk {
|
||||
this.lastReadAt = fasttime.Now().Unix()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close implement Close() for net.Conn interface
|
||||
func (this *OriginConn) Close() error {
|
||||
if this.lastReadOk && fasttime.Now().Unix()-this.lastReadAt <= originConnCloseDelaySeconds {
|
||||
closingOriginConnLocker.Lock()
|
||||
closingOriginConnMap[this] = zero.Zero{}
|
||||
closingOriginConnLocker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = true
|
||||
return this.Conn.Close()
|
||||
}
|
||||
|
||||
func (this *OriginConn) ForceClose() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = true
|
||||
return this.Conn.Close()
|
||||
}
|
||||
|
||||
func (this *OriginConn) IsExpired() bool {
|
||||
return fasttime.Now().Unix()-this.lastReadAt > originConnCloseDelaySeconds
|
||||
}
|
||||
@@ -53,7 +53,11 @@ func (this *OCSPUpdateTask) Start() {
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Warn("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Debug("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||
} else {
|
||||
remotelogs.Warn("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPC.DisableUpdate {
|
||||
if config.RPCDisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
if utils.EqualStrings(newEndpoints, config.RPC.Endpoints) {
|
||||
if utils.EqualStrings(newEndpoints, config.RPCEndpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPC.Endpoints = newEndpoints
|
||||
config.RPCEndpoints = newEndpoints
|
||||
|
||||
// 更新当前RPC
|
||||
if !hasCustomizedAPINodeAddrs {
|
||||
@@ -118,7 +118,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ var sharedUpgradeManager = NewUpgradeManager()
|
||||
type UpgradeManager struct {
|
||||
isInstalling bool
|
||||
lastFile string
|
||||
exe string
|
||||
}
|
||||
|
||||
// NewUpgradeManager 获取新对象
|
||||
@@ -38,6 +39,14 @@ func NewUpgradeManager() *UpgradeManager {
|
||||
|
||||
// Start 启动升级
|
||||
func (this *UpgradeManager) Start() {
|
||||
// 必须放在文件解压之前读取可执行文件路径,防止解析之后,当前的可执行文件路径发生改变
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
remotelogs.Error("UPGRADE_MANAGER", "can not find current executable file name")
|
||||
return
|
||||
}
|
||||
this.exe = exe
|
||||
|
||||
// 测试环境下不更新
|
||||
if Tea.IsTesting() {
|
||||
return
|
||||
@@ -49,7 +58,7 @@ func (this *UpgradeManager) Start() {
|
||||
this.isInstalling = true
|
||||
|
||||
remotelogs.Println("UPGRADE_MANAGER", "upgrading node ...")
|
||||
err := this.install()
|
||||
err = this.install()
|
||||
if err != nil {
|
||||
remotelogs.Error("UPGRADE_MANAGER", "download failed: "+err.Error())
|
||||
|
||||
@@ -104,7 +113,7 @@ func (this *UpgradeManager) install() error {
|
||||
|
||||
remotelogs.Println("UPGRADE_MANAGER", "downloading new node ...")
|
||||
|
||||
var path = dir + "/edge-node" + ".tmp"
|
||||
var path = dir + "/edge-node.tmp"
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -238,11 +247,6 @@ func (this *UpgradeManager) restart() error {
|
||||
if DaemonIsOn && DaemonPid == os.Getppid() {
|
||||
utils.Exit() // TODO 试着更优雅重启
|
||||
} else {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// quit
|
||||
events.Notify(events.EventQuit)
|
||||
|
||||
@@ -250,10 +254,9 @@ func (this *UpgradeManager) restart() error {
|
||||
events.Notify(events.EventTerminated)
|
||||
|
||||
// 启动
|
||||
exe = filepath.Dir(exe) + "/" + teaconst.ProcessName
|
||||
|
||||
var exe = filepath.Dir(this.exe) + "/" + teaconst.ProcessName
|
||||
var cmd = executils.NewCmd(exe, "start")
|
||||
err = cmd.Start()
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func BenchmarkRegexp_MatchString(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkRegexp_MatchString2(b *testing.B) {
|
||||
var r = regexp.MustCompile("(?i)(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick|onkeydown|onkeyup|onkeypress)(\\s|%09|%0A|(\\+|%20))*(=|%3D)")
|
||||
var r = regexp.MustCompile(`(?i)(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick|onkeydown|onkeyup|onkeypress)(\s|%09|%0A|(\+|%20))*(=|%3D)`)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -60,7 +60,6 @@ func Println(tag string, description string) {
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +77,6 @@ func Warn(tag string, description string) {
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -228,10 +229,10 @@ func (this *RPCClient) TestEndpoints(endpoints []string) bool {
|
||||
func (this *RPCClient) init() error {
|
||||
// 重新连接
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPC.Endpoints {
|
||||
for _, endpoint := range this.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return errors.New("parse endpoint failed: " + err.Error())
|
||||
return fmt.Errorf("parse endpoint failed: %w", err)
|
||||
}
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
|
||||
@@ -132,9 +132,10 @@ func (this *BandwidthStatManager) Loop() error {
|
||||
for key, stat := range this.m {
|
||||
if stat.Day < day || stat.TimeAt < currentTime {
|
||||
// 防止数据出现错误
|
||||
if stat.CachedBytes > stat.TotalBytes {
|
||||
if stat.CachedBytes > stat.TotalBytes || stat.CountCachedRequests == stat.CountRequests {
|
||||
stat.CachedBytes = stat.TotalBytes
|
||||
}
|
||||
|
||||
if stat.AttackBytes > stat.TotalBytes {
|
||||
stat.AttackBytes = stat.TotalBytes
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ func BenchmarkBandwidthStatManager_Slice(b *testing.B) {
|
||||
NodeRegionId: 1,
|
||||
})
|
||||
}
|
||||
_ = pbStats
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ func BenchmarkBandwidthStatManager_Slice2(b *testing.B) {
|
||||
var stat = &stats.BandwidthStat{}
|
||||
statsSlice = append(statsSlice, stat)
|
||||
}
|
||||
_ = statsSlice
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ func (this *TrafficItem) Add(anotherItem *TrafficItem) {
|
||||
this.AttackBytes += anotherItem.AttackBytes
|
||||
}
|
||||
|
||||
const trafficStatsMaxLife = 1200 // 最大只保存20分钟内的数据
|
||||
|
||||
// TrafficStatManager 区域流量统计
|
||||
type TrafficStatManager struct {
|
||||
itemMap map[string]*TrafficItem // [timestamp serverId] => *TrafficItem
|
||||
@@ -231,6 +229,12 @@ func (this *TrafficStatManager) Upload() error {
|
||||
if len(pieces) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 修正数据
|
||||
if item.CachedBytes > item.Bytes || item.CountCachedRequests == item.CountRequests {
|
||||
item.CachedBytes = item.Bytes
|
||||
}
|
||||
|
||||
var pbItem = &pb.UploadServerDailyStatsRequest_DomainStat{
|
||||
ServerId: serverId,
|
||||
Domain: pieces[1],
|
||||
|
||||
@@ -13,7 +13,6 @@ var SharedCache = NewBigCache()
|
||||
//
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
// [ Item1, Item2, ... ] | ...
|
||||
//
|
||||
type Cache struct {
|
||||
isDestroyed bool
|
||||
pieces []*Piece
|
||||
@@ -123,19 +122,11 @@ func (this *Cache) Read(key string) (item *Item) {
|
||||
return this.pieces[uint64Key%this.countPieces].Read(uint64Key)
|
||||
}
|
||||
|
||||
func (this *Cache) readIntKey(key uint64) (value *Item) {
|
||||
return this.pieces[key%this.countPieces].Read(key)
|
||||
}
|
||||
|
||||
func (this *Cache) Delete(key string) {
|
||||
var uint64Key = HashKey([]byte(key))
|
||||
this.pieces[uint64Key%this.countPieces].Delete(uint64Key)
|
||||
}
|
||||
|
||||
func (this *Cache) deleteIntKey(key uint64) {
|
||||
this.pieces[key%this.countPieces].Delete(key)
|
||||
}
|
||||
|
||||
func (this *Cache) Count() (count int) {
|
||||
for _, piece := range this.pieces {
|
||||
count += piece.Count()
|
||||
|
||||
@@ -61,6 +61,8 @@ func BenchmarkBytePool_Get_Sync(b *testing.B) {
|
||||
return make([]byte, 1024)
|
||||
},
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
|
||||
@@ -4,7 +4,7 @@ package clock
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -108,7 +108,7 @@ func (this *ClockManager) Sync() error {
|
||||
if err == nil {
|
||||
currentTime, err := this.ReadServer(server)
|
||||
if err != nil {
|
||||
return errors.New("read server failed: " + err.Error())
|
||||
return fmt.Errorf("read server failed: %w", err)
|
||||
}
|
||||
|
||||
var delta = time.Now().Unix() - currentTime.Unix()
|
||||
@@ -135,7 +135,7 @@ func (this *ClockManager) syncNtpdate(ntpdate string, server string) error {
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ": " + cmd.Stderr())
|
||||
return fmt.Errorf("%w: %s", err, cmd.Stderr())
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -145,7 +145,7 @@ func (this *ClockManager) syncNtpdate(ntpdate string, server string) error {
|
||||
func (this *ClockManager) ReadServer(server string) (time.Time, error) {
|
||||
conn, err := net.Dial("udp", server+":123")
|
||||
if err != nil {
|
||||
return time.Time{}, errors.New("connect to server failed: " + err.Error())
|
||||
return time.Time{}, fmt.Errorf("connect to server failed: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
@@ -164,13 +164,13 @@ func (this *ClockManager) ReadServer(server string) (time.Time, error) {
|
||||
var req = &NTPPacket{Settings: 0x1B}
|
||||
err = binary.Write(conn, binary.BigEndian, req)
|
||||
if err != nil {
|
||||
return time.Time{}, errors.New("write request failed: " + err.Error())
|
||||
return time.Time{}, fmt.Errorf("write request failed: %w", err)
|
||||
}
|
||||
|
||||
var resp = &NTPPacket{}
|
||||
err = binary.Read(conn, binary.BigEndian, resp)
|
||||
if err != nil {
|
||||
return time.Time{}, errors.New("write server response failed: " + err.Error())
|
||||
return time.Time{}, fmt.Errorf("write server response failed: %w", err)
|
||||
}
|
||||
|
||||
const ntpEpochOffset = 2208988800
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fileutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -19,8 +20,9 @@ import (
|
||||
var errDBIsClosed = errors.New("the database is closed")
|
||||
|
||||
type DB struct {
|
||||
locker *fileutils.Locker
|
||||
locker *fsutils.Locker
|
||||
rawDB *sql.DB
|
||||
dsn string
|
||||
|
||||
statusLocker sync.Mutex
|
||||
countUpdating int32
|
||||
@@ -41,8 +43,12 @@ func OpenReader(dsn string) (*DB, error) {
|
||||
}
|
||||
|
||||
func open(dsn string, lock bool) (*DB, error) {
|
||||
if teaconst.IsQuiting {
|
||||
return nil, errors.New("can not open database when process is quiting")
|
||||
}
|
||||
|
||||
// locker
|
||||
var locker *fileutils.Locker
|
||||
var locker *fsutils.Locker
|
||||
if lock {
|
||||
var path = dsn
|
||||
var queryIndex = strings.Index(dsn, "?")
|
||||
@@ -50,7 +56,7 @@ func open(dsn string, lock bool) (*DB, error) {
|
||||
path = path[:queryIndex]
|
||||
}
|
||||
path = strings.TrimSpace(strings.TrimPrefix(path, "file:"))
|
||||
locker = fileutils.NewLocker(path)
|
||||
locker = fsutils.NewLocker(path)
|
||||
err := locker.Lock()
|
||||
if err != nil {
|
||||
remotelogs.Warn("DB", "lock '"+path+"' failed: "+err.Error())
|
||||
@@ -64,14 +70,15 @@ func open(dsn string, lock bool) (*DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var db = NewDB(rawDB)
|
||||
var db = NewDB(rawDB, dsn)
|
||||
db.locker = locker
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func NewDB(rawDB *sql.DB) *DB {
|
||||
func NewDB(rawDB *sql.DB, dsn string) *DB {
|
||||
var db = &DB{
|
||||
rawDB: rawDB,
|
||||
dsn: dsn,
|
||||
}
|
||||
|
||||
events.OnKey(events.EventQuit, fmt.Sprintf("db_%p", db), func() {
|
||||
@@ -116,7 +123,7 @@ func (this *DB) Prepare(query string) (*Stmt, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (this *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||
func (this *DB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
|
||||
// check database status
|
||||
if this.BeginUpdating() {
|
||||
defer this.EndUpdating()
|
||||
@@ -131,7 +138,7 @@ func (this *DB) ExecContext(ctx context.Context, query string, args ...interface
|
||||
return this.rawDB.ExecContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
func (this *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
func (this *DB) Exec(query string, args ...any) (sql.Result, error) {
|
||||
// check database status
|
||||
if this.BeginUpdating() {
|
||||
defer this.EndUpdating()
|
||||
@@ -145,14 +152,14 @@ func (this *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return this.rawDB.Exec(query, args...)
|
||||
}
|
||||
|
||||
func (this *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
func (this *DB) Query(query string, args ...any) (*sql.Rows, error) {
|
||||
if this.enableStat {
|
||||
defer SharedQueryStatManager.AddQuery(query).End()
|
||||
}
|
||||
return this.rawDB.Query(query, args...)
|
||||
}
|
||||
|
||||
func (this *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
func (this *DB) QueryRow(query string, args ...any) *sql.Row {
|
||||
if this.enableStat {
|
||||
defer SharedQueryStatManager.AddQuery(query).End()
|
||||
}
|
||||
@@ -193,6 +200,14 @@ func (this *DB) Close() error {
|
||||
}
|
||||
}()
|
||||
|
||||
// print log
|
||||
/**if len(this.dsn) > 0 {
|
||||
u, _ := url.Parse(this.dsn)
|
||||
if u != nil && len(u.Path) > 0 {
|
||||
remotelogs.Debug("DB", "close '"+u.Path)
|
||||
}
|
||||
}**/
|
||||
|
||||
return this.rawDB.Close()
|
||||
}
|
||||
|
||||
|
||||
17
internal/utils/dbs/db_test.go
Normal file
17
internal/utils/dbs/db_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dbs_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseDSN(t *testing.T) {
|
||||
var dsn = "file:/home/cache/p43/.indexes/db-3.db?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=88000"
|
||||
u, err := url.Parse(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(u.Path) // expect: :/home/cache/p43/.indexes/db-3.db
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func (this *Stmt) EnableStat() {
|
||||
this.enableStat = true
|
||||
}
|
||||
|
||||
func (this *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
|
||||
func (this *Stmt) ExecContext(ctx context.Context, args ...any) (sql.Result, error) {
|
||||
// check database status
|
||||
if this.db.BeginUpdating() {
|
||||
defer this.db.EndUpdating()
|
||||
@@ -41,7 +41,7 @@ func (this *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Res
|
||||
return this.rawStmt.ExecContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (this *Stmt) Exec(args ...interface{}) (sql.Result, error) {
|
||||
func (this *Stmt) Exec(args ...any) (sql.Result, error) {
|
||||
// check database status
|
||||
if this.db.BeginUpdating() {
|
||||
defer this.db.EndUpdating()
|
||||
@@ -55,28 +55,37 @@ func (this *Stmt) Exec(args ...interface{}) (sql.Result, error) {
|
||||
return this.rawStmt.Exec(args...)
|
||||
}
|
||||
|
||||
func (this *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) {
|
||||
func (this *Stmt) QueryContext(ctx context.Context, args ...any) (*sql.Rows, error) {
|
||||
if this.enableStat {
|
||||
defer SharedQueryStatManager.AddQuery(this.query).End()
|
||||
}
|
||||
return this.rawStmt.QueryContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (this *Stmt) Query(args ...interface{}) (*sql.Rows, error) {
|
||||
func (this *Stmt) Query(args ...any) (*sql.Rows, error) {
|
||||
if this.enableStat {
|
||||
defer SharedQueryStatManager.AddQuery(this.query).End()
|
||||
}
|
||||
return this.rawStmt.Query(args...)
|
||||
rows, err := this.rawStmt.Query(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rowsErr = rows.Err()
|
||||
if rowsErr != nil {
|
||||
_ = rows.Close()
|
||||
return nil, rowsErr
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (this *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row {
|
||||
func (this *Stmt) QueryRowContext(ctx context.Context, args ...any) *sql.Row {
|
||||
if this.enableStat {
|
||||
defer SharedQueryStatManager.AddQuery(this.query).End()
|
||||
}
|
||||
return this.rawStmt.QueryRowContext(ctx, args...)
|
||||
}
|
||||
|
||||
func (this *Stmt) QueryRow(args ...interface{}) *sql.Row {
|
||||
func (this *Stmt) QueryRow(args ...any) *sql.Row {
|
||||
if this.enableStat {
|
||||
defer SharedQueryStatManager.AddQuery(this.query).End()
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestList_ManyItems(t *testing.T) {
|
||||
})
|
||||
list.GC(time.Now().Unix() + 1)
|
||||
t.Log("gc", count, "items")
|
||||
t.Log(time.Now().Sub(now))
|
||||
t.Log(time.Since(now))
|
||||
}
|
||||
|
||||
func TestList_Map_Performance(t *testing.T) {
|
||||
@@ -140,7 +140,7 @@ func TestList_Map_Performance(t *testing.T) {
|
||||
for i := 0; i < 100_000; i++ {
|
||||
delete(m, int64(i))
|
||||
}
|
||||
t.Log(time.Now().Sub(now))
|
||||
t.Log(time.Since(now))
|
||||
}
|
||||
|
||||
{
|
||||
@@ -153,7 +153,7 @@ func TestList_Map_Performance(t *testing.T) {
|
||||
for i := 0; i < 100_000; i++ {
|
||||
delete(m, uint64(i))
|
||||
}
|
||||
t.Log(time.Now().Sub(now))
|
||||
t.Log(time.Since(now))
|
||||
}
|
||||
|
||||
{
|
||||
@@ -166,7 +166,7 @@ func TestList_Map_Performance(t *testing.T) {
|
||||
for i := 0; i < 100_000; i++ {
|
||||
delete(m, uint32(i))
|
||||
}
|
||||
t.Log(time.Now().Sub(now))
|
||||
t.Log(time.Since(now))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var SharedFreeHoursManager = NewFreeHoursManager()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
SharedFreeHoursManager.Start()
|
||||
})
|
||||
})
|
||||
events.OnClose(func() {
|
||||
SharedFreeHoursManager.Stop()
|
||||
})
|
||||
}
|
||||
|
||||
// FreeHoursManager 计算节点空闲时间
|
||||
// 以便于我们在空闲时间执行高强度的任务,如清理缓存等
|
||||
type FreeHoursManager struct {
|
||||
dayTrafficMap map[int][24]uint64 // day => [ traffic bytes ]
|
||||
lastBytes uint64
|
||||
|
||||
freeHours []int
|
||||
count int
|
||||
|
||||
locker sync.Mutex
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
func NewFreeHoursManager() *FreeHoursManager {
|
||||
return &FreeHoursManager{dayTrafficMap: map[int][24]uint64{}, count: 3}
|
||||
}
|
||||
|
||||
func (this *FreeHoursManager) Start() {
|
||||
this.ticker = time.NewTicker(30 * time.Minute)
|
||||
for range this.ticker.C {
|
||||
this.Update(atomic.LoadUint64(&teaconst.InTrafficBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FreeHoursManager) Update(bytes uint64) {
|
||||
if this.count <= 0 {
|
||||
this.count = 3
|
||||
}
|
||||
|
||||
if this.lastBytes == 0 {
|
||||
this.lastBytes = bytes
|
||||
} else {
|
||||
// 记录流量
|
||||
var deltaBytes = bytes - this.lastBytes
|
||||
var now = time.Now()
|
||||
var day = now.Day()
|
||||
var hour = now.Hour()
|
||||
traffic, ok := this.dayTrafficMap[day]
|
||||
if ok {
|
||||
traffic[hour] += deltaBytes
|
||||
} else {
|
||||
var traffic = [24]uint64{}
|
||||
traffic[hour] += deltaBytes
|
||||
this.dayTrafficMap[day] = traffic
|
||||
}
|
||||
|
||||
this.lastBytes = bytes
|
||||
|
||||
// 计算空闲时间
|
||||
var result = [24]uint64{}
|
||||
var hasData = false
|
||||
for trafficDay, trafficArray := range this.dayTrafficMap {
|
||||
// 当天的不算
|
||||
if trafficDay == day {
|
||||
continue
|
||||
}
|
||||
|
||||
// 查看最近5天的
|
||||
if (day > trafficDay && day-trafficDay <= 5) || (day < trafficDay && trafficDay-day >= 26) {
|
||||
var weights = this.sortUintArrayWeights(trafficArray)
|
||||
for k, v := range weights {
|
||||
result[k] += v
|
||||
}
|
||||
hasData = true
|
||||
}
|
||||
}
|
||||
if hasData {
|
||||
var freeHours = this.sortUintArrayIndexes(result)
|
||||
this.locker.Lock()
|
||||
this.freeHours = freeHours[:this.count] // 取前N个小时作为空闲时间
|
||||
this.locker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FreeHoursManager) IsFreeHour() bool {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
if len(this.freeHours) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var hour = time.Now().Hour()
|
||||
for _, h := range this.freeHours {
|
||||
if h == hour {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *FreeHoursManager) Stop() {
|
||||
if this.ticker != nil {
|
||||
this.ticker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// 对数组进行排序,并返回权重
|
||||
func (this *FreeHoursManager) sortUintArrayWeights(arr [24]uint64) [24]uint64 {
|
||||
var l = []map[string]interface{}{}
|
||||
for k, v := range arr {
|
||||
l = append(l, map[string]interface{}{
|
||||
"k": k,
|
||||
"v": v,
|
||||
})
|
||||
}
|
||||
sort.Slice(l, func(i, j int) bool {
|
||||
var m1 = l[i]
|
||||
var v1 = m1["v"].(uint64)
|
||||
|
||||
var m2 = l[j]
|
||||
var v2 = m2["v"].(uint64)
|
||||
|
||||
return v1 < v2
|
||||
})
|
||||
|
||||
var result = [24]uint64{}
|
||||
for k, v := range l {
|
||||
if k < this.count {
|
||||
k = 0
|
||||
} else {
|
||||
k = 1
|
||||
}
|
||||
result[v["k"].(int)] = v["v"].(uint64)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 对数组进行排序,并返回索引
|
||||
func (this *FreeHoursManager) sortUintArrayIndexes(arr [24]uint64) [24]int {
|
||||
var l = []map[string]interface{}{}
|
||||
for k, v := range arr {
|
||||
l = append(l, map[string]interface{}{
|
||||
"k": k,
|
||||
"v": v,
|
||||
})
|
||||
}
|
||||
sort.Slice(l, func(i, j int) bool {
|
||||
var m1 = l[i]
|
||||
var v1 = m1["v"].(uint64)
|
||||
|
||||
var m2 = l[j]
|
||||
var v2 = m2["v"].(uint64)
|
||||
|
||||
return v1 < v2
|
||||
})
|
||||
|
||||
var result = [24]int{}
|
||||
var i = 0
|
||||
for _, v := range l {
|
||||
result[i] = v["k"].(int)
|
||||
i++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFreeHoursManager_Update(t *testing.T) {
|
||||
var manager = NewFreeHoursManager()
|
||||
manager.Update(111)
|
||||
|
||||
manager.dayTrafficMap[1] = [24]uint64{1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1}
|
||||
manager.dayTrafficMap[2] = [24]uint64{0, 0, 1, 0, 1, 1, 1, 0, 0}
|
||||
manager.dayTrafficMap[3] = [24]uint64{0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}
|
||||
manager.dayTrafficMap[4] = [24]uint64{0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1}
|
||||
manager.dayTrafficMap[5] = [24]uint64{0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1}
|
||||
manager.dayTrafficMap[6] = [24]uint64{0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1}
|
||||
manager.dayTrafficMap[7] = [24]uint64{}
|
||||
manager.dayTrafficMap[8] = [24]uint64{}
|
||||
manager.dayTrafficMap[9] = [24]uint64{}
|
||||
manager.dayTrafficMap[10] = [24]uint64{1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
manager.dayTrafficMap[11] = [24]uint64{1}
|
||||
manager.dayTrafficMap[12] = [24]uint64{1}
|
||||
manager.dayTrafficMap[13] = [24]uint64{1}
|
||||
manager.dayTrafficMap[14] = [24]uint64{}
|
||||
manager.dayTrafficMap[15] = [24]uint64{}
|
||||
manager.dayTrafficMap[16] = [24]uint64{}
|
||||
manager.dayTrafficMap[25] = [24]uint64{}
|
||||
manager.dayTrafficMap[26] = [24]uint64{}
|
||||
manager.dayTrafficMap[27] = [24]uint64{}
|
||||
manager.dayTrafficMap[28] = [24]uint64{}
|
||||
manager.dayTrafficMap[29] = [24]uint64{}
|
||||
manager.dayTrafficMap[30] = [24]uint64{}
|
||||
manager.dayTrafficMap[31] = [24]uint64{}
|
||||
|
||||
var before = time.Now()
|
||||
manager.Update(222)
|
||||
t.Log(manager.freeHours)
|
||||
t.Log(manager.IsFreeHour())
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestFreeHoursManager_SortArray(t *testing.T) {
|
||||
var manager = NewFreeHoursManager()
|
||||
t.Log(manager.sortUintArrayWeights([24]uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 109, 10, 11, 12, 130, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}))
|
||||
t.Log(manager.sortUintArrayIndexes([24]uint64{1, 2, 3, 5, 4, 0, 100}))
|
||||
}
|
||||
87
internal/utils/fs/disk.go
Normal file
87
internal/utils/fs/disk.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fsutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CheckDiskWritingSpeed test disk writing speed
|
||||
func CheckDiskWritingSpeed() (speedMB float64, err error) {
|
||||
var tempDir = os.TempDir()
|
||||
if len(tempDir) == 0 {
|
||||
tempDir = "/tmp"
|
||||
}
|
||||
|
||||
const filename = "edge-disk-writing-test.data"
|
||||
var path = tempDir + "/" + filename
|
||||
_ = os.Remove(path) // always try to delete the file
|
||||
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var isClosed bool
|
||||
defer func() {
|
||||
if !isClosed {
|
||||
_ = fp.Close()
|
||||
}
|
||||
|
||||
_ = os.Remove(path)
|
||||
}()
|
||||
|
||||
var data = bytes.Repeat([]byte{'A'}, 16<<20)
|
||||
var before = time.Now()
|
||||
_, err = fp.Write(data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = fp.Sync()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = fp.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var costSeconds = time.Since(before).Seconds()
|
||||
speedMB = float64(len(data)) / (1 << 20) / costSeconds
|
||||
speedMB = math.Ceil(speedMB/10) * 10
|
||||
|
||||
isClosed = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckDiskIsFast check disk is 'fast' disk to write
|
||||
func CheckDiskIsFast() (speedMB float64, isFast bool, err error) {
|
||||
speedMB, err = CheckDiskWritingSpeed()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
isFast = speedMB > 150
|
||||
|
||||
if speedMB > 1000 {
|
||||
DiskSpeed = SpeedExtremelyFast
|
||||
} else if speedMB > 150 {
|
||||
DiskSpeed = SpeedFast
|
||||
} else if speedMB > 60 {
|
||||
DiskSpeed = SpeedLow
|
||||
} else {
|
||||
DiskSpeed = SpeedExtremelySlow
|
||||
}
|
||||
calculateDiskMaxWrites()
|
||||
|
||||
if speedMB > DiskSpeedMB {
|
||||
DiskSpeedMB = speedMB
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
16
internal/utils/fs/disk_test_test.go
Normal file
16
internal/utils/fs/disk_test_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fsutils_test
|
||||
|
||||
import (
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckDiskWritingSpeed(t *testing.T) {
|
||||
t.Log(fsutils.CheckDiskWritingSpeed())
|
||||
}
|
||||
|
||||
func TestCheckDiskIsFast(t *testing.T) {
|
||||
t.Log(fsutils.CheckDiskIsFast())
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fileutils
|
||||
package fsutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,22 +1,22 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fileutils_test
|
||||
package fsutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fileutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocker_Lock(t *testing.T) {
|
||||
var path = "/tmp/file-test"
|
||||
var locker = fileutils.NewLocker(path)
|
||||
var locker = fsutils.NewLocker(path)
|
||||
err := locker.Lock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = locker.Release()
|
||||
|
||||
var locker2 = fileutils.NewLocker(path)
|
||||
var locker2 = fsutils.NewLocker(path)
|
||||
err = locker2.Lock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
100
internal/utils/fs/status.go
Normal file
100
internal/utils/fs/status.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fsutils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Speed int
|
||||
|
||||
func (this Speed) String() string {
|
||||
switch this {
|
||||
case SpeedExtremelyFast:
|
||||
return "extremely fast"
|
||||
case SpeedFast:
|
||||
return "fast"
|
||||
case SpeedLow:
|
||||
return "low"
|
||||
case SpeedExtremelySlow:
|
||||
return "extremely slow"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
const (
|
||||
SpeedExtremelyFast Speed = 1
|
||||
SpeedFast Speed = 2
|
||||
SpeedLow Speed = 3
|
||||
SpeedExtremelySlow Speed = 4
|
||||
)
|
||||
|
||||
var (
|
||||
DiskSpeed = SpeedLow
|
||||
DiskMaxWrites int32 = 32
|
||||
DiskSpeedMB float64
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
// test disk
|
||||
go func() {
|
||||
// initial check
|
||||
_, _, _ = CheckDiskIsFast()
|
||||
|
||||
// check every one hour
|
||||
var ticker = time.NewTicker(1 * time.Hour)
|
||||
var count = 0
|
||||
for range ticker.C {
|
||||
_, _, err := CheckDiskIsFast()
|
||||
if err == nil {
|
||||
count++
|
||||
if count > 24 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func DiskIsFast() bool {
|
||||
return DiskSpeed == SpeedExtremelyFast || DiskSpeed == SpeedFast
|
||||
}
|
||||
|
||||
func DiskIsExtremelyFast() bool {
|
||||
return DiskSpeed == SpeedExtremelyFast
|
||||
}
|
||||
|
||||
var countWrites int32 = 0
|
||||
|
||||
func WriteReady() bool {
|
||||
return atomic.LoadInt32(&countWrites) < DiskMaxWrites
|
||||
}
|
||||
|
||||
func WriteBegin() {
|
||||
atomic.AddInt32(&countWrites, 1)
|
||||
}
|
||||
|
||||
func WriteEnd() {
|
||||
atomic.AddInt32(&countWrites, -1)
|
||||
}
|
||||
|
||||
func calculateDiskMaxWrites() {
|
||||
switch DiskSpeed {
|
||||
case SpeedExtremelyFast:
|
||||
DiskMaxWrites = 256
|
||||
case SpeedFast:
|
||||
DiskMaxWrites = 128
|
||||
case SpeedLow:
|
||||
DiskMaxWrites = 32
|
||||
case SpeedExtremelySlow:
|
||||
DiskMaxWrites = 16
|
||||
default:
|
||||
DiskMaxWrites = 16
|
||||
}
|
||||
}
|
||||
31
internal/utils/fs/status_test.go
Normal file
31
internal/utils/fs/status_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package fsutils_test
|
||||
|
||||
import (
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWrites(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
for i := 0; i < int(fsutils.DiskMaxWrites); i++ {
|
||||
fsutils.WriteBegin()
|
||||
}
|
||||
a.IsFalse(fsutils.WriteReady())
|
||||
|
||||
fsutils.WriteEnd()
|
||||
a.IsTrue(fsutils.WriteReady())
|
||||
}
|
||||
|
||||
func BenchmarkWrites(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
fsutils.WriteReady()
|
||||
fsutils.WriteBegin()
|
||||
fsutils.WriteEnd()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -34,10 +34,7 @@ func IsLocalIP(ipString string) bool {
|
||||
|
||||
// IPv6
|
||||
if strings.Contains(ipString, ":") {
|
||||
if ip.String() == "::1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return ip.String() == "::1"
|
||||
}
|
||||
|
||||
// IPv4
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user