Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d82c03db23 | ||
|
|
230c5c3766 | ||
|
|
927425149e | ||
|
|
5ce1aab92c | ||
|
|
195742bb26 | ||
|
|
006cc2912d | ||
|
|
2d4ba90c3b | ||
|
|
a2e6aaaa18 | ||
|
|
8e68da7725 | ||
|
|
7abb84c880 | ||
|
|
a17878f5b2 | ||
|
|
8a8881ac47 | ||
|
|
c567404b7a | ||
|
|
b220b0f48e | ||
|
|
9609c90d75 | ||
|
|
2c3c32af5b | ||
|
|
b4a4b2e9b1 | ||
|
|
c42ff1e1e9 | ||
|
|
9fed1141c2 | ||
|
|
e87f031293 | ||
|
|
c4bac7f43c | ||
|
|
47818f972e | ||
|
|
218a0300c5 | ||
|
|
63f6c4177f | ||
|
|
1830c22a31 | ||
|
|
18611e8a7c | ||
|
|
c45f7adf04 | ||
|
|
1a200918a8 | ||
|
|
b942bb776e | ||
|
|
5cf84efccd | ||
|
|
ebb6ebd10c | ||
|
|
42d0d63cf4 | ||
|
|
96f8f7e925 | ||
|
|
e7e7214d58 | ||
|
|
ade979a725 | ||
|
|
60a8de13e7 | ||
|
|
9fa24bed0a | ||
|
|
87bc1a7e03 | ||
|
|
1a05f56149 | ||
|
|
f88db576e1 | ||
|
|
dc3f26ea1a | ||
|
|
6fc30144f7 | ||
|
|
25b0b98bd4 | ||
|
|
27b5817d5e | ||
|
|
dcb61dfd33 | ||
|
|
bbcfdbbf5e | ||
|
|
b2a1bef08f | ||
|
|
2b18b5c2ca | ||
|
|
6ff030dbd8 | ||
|
|
0ddeef6986 | ||
|
|
976bd3600b | ||
|
|
a64047a934 | ||
|
|
e82f207935 | ||
|
|
61b5316a1f | ||
|
|
82329aa8b0 | ||
|
|
7dabd9c19c | ||
|
|
9437acd18c | ||
|
|
9da7a34edf | ||
|
|
b6a5491dcc | ||
|
|
bcee658567 | ||
|
|
afc8f7b703 | ||
|
|
7a4b89d2fb | ||
|
|
c6299a2fb0 | ||
|
|
8b5d74af9b | ||
|
|
a194360a56 | ||
|
|
b12f7f69ba | ||
|
|
06ec4d3fba | ||
|
|
c209ab912f | ||
|
|
32720d772d | ||
|
|
a89c02fd10 | ||
|
|
37ef86b92f | ||
|
|
4c19c37f49 | ||
|
|
1bb818b5b0 | ||
|
|
825e46458f | ||
|
|
a42737bd28 | ||
|
|
5f76be2cfd | ||
|
|
dbddf8a91a | ||
|
|
6c457f41f6 | ||
|
|
e4b2a650f0 | ||
|
|
913ba95801 | ||
|
|
a9f8e39703 | ||
|
|
534f013f59 | ||
|
|
258380f75c | ||
|
|
8c0e51ec46 | ||
|
|
4c37c7ab84 | ||
|
|
f005da1d5f | ||
|
|
e99acc4694 | ||
|
|
408357dfcf | ||
|
|
0109a27c06 | ||
|
|
e6e2dccc42 | ||
|
|
09dcf0d712 | ||
|
|
60aebd9306 | ||
|
|
04191d04d3 | ||
|
|
b80a5c525f | ||
|
|
265c1e5312 | ||
|
|
2723f705b6 | ||
|
|
b4cddd6341 | ||
|
|
5636a81d48 | ||
|
|
d8059960de | ||
|
|
17af4064af | ||
|
|
15f37d2c93 | ||
|
|
6dc3aa8cb7 | ||
|
|
900cccf2f1 | ||
|
|
1fec88dfc6 | ||
|
|
7da9363336 | ||
|
|
d82e633bba | ||
|
|
b363bbaafd | ||
|
|
92a20e3c9a | ||
|
|
5742dfb263 | ||
|
|
0ae63511d5 | ||
|
|
aa60092c20 | ||
|
|
54fc265d24 | ||
|
|
a5ac900784 | ||
|
|
4053f1da32 | ||
|
|
0374ccd8a8 | ||
|
|
1d46c446cf | ||
|
|
54b66805f9 | ||
|
|
f7afcbde92 | ||
|
|
8bec1cf68e | ||
|
|
2cd1bb7f95 | ||
|
|
19e6329a2b | ||
|
|
fce2879567 | ||
|
|
0973765919 | ||
|
|
827679721e | ||
|
|
735279bc7a | ||
|
|
3eb2ed9897 | ||
|
|
3a913d98c7 | ||
|
|
9bfcd79e36 | ||
|
|
a81d610302 | ||
|
|
64b1753c4d | ||
|
|
afcb5c2957 | ||
|
|
7d0b9208a3 | ||
|
|
c0f0ec43bb | ||
|
|
30bd66958c | ||
|
|
fafac1a038 | ||
|
|
f979f9503e | ||
|
|
b233c3cc7a | ||
|
|
597ac936f7 | ||
|
|
a590254eb3 | ||
|
|
0498bcf30c | ||
|
|
59f9b5c724 | ||
|
|
80729935b6 | ||
|
|
4ca57fb99c | ||
|
|
9b35902ad4 | ||
|
|
3b8bd09190 | ||
|
|
71a5bc0652 | ||
|
|
ac6a8c4e85 | ||
|
|
f58a808c3a | ||
|
|
51037be772 | ||
|
|
443ff9aff7 |
@@ -50,6 +50,7 @@ function build() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
||||||
|
cp "$ROOT"/configs/cluster.template.yaml "$DIST"/configs
|
||||||
cp -R "$ROOT"/www "$DIST"/
|
cp -R "$ROOT"/www "$DIST"/
|
||||||
cp -R "$ROOT"/pages "$DIST"/
|
cp -R "$ROOT"/pages "$DIST"/
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func main() {
|
|||||||
Product(teaconst.ProductName).
|
Product(teaconst.ProductName).
|
||||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
|
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
|
||||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
||||||
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove] IP")
|
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
|
||||||
|
|
||||||
app.On("test", func() {
|
app.On("test", func() {
|
||||||
err := nodes.NewNode().Test()
|
err := nodes.NewNode().Test()
|
||||||
@@ -241,6 +241,38 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
app.On("ip.close", func() {
|
||||||
|
var args = os.Args[2:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Println("Usage: edge-node ip.close IP")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ip = args[0]
|
||||||
|
if len(net.ParseIP(ip)) == 0 {
|
||||||
|
fmt.Println("IP '" + ip + "' is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("close ip '" + ip)
|
||||||
|
|
||||||
|
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||||
|
reply, err := sock.Send(&gosock.Command{
|
||||||
|
Code: "closeIP",
|
||||||
|
Params: map[string]any{
|
||||||
|
"ip": ip,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[ERROR]" + err.Error())
|
||||||
|
} else {
|
||||||
|
var errString = maps.NewMap(reply.Params).GetString("error")
|
||||||
|
if len(errString) > 0 {
|
||||||
|
fmt.Println("[ERROR]" + errString)
|
||||||
|
} else {
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
app.On("ip.remove", func() {
|
app.On("ip.remove", func() {
|
||||||
var args = os.Args[2:]
|
var args = os.Args[2:]
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (this *LogWriter) Init() {
|
|||||||
this.c = make(chan string, 1024)
|
this.c = make(chan string, 1024)
|
||||||
|
|
||||||
// 异步写入文件
|
// 异步写入文件
|
||||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
var maxFileSize = 128 * sizes.M // 文件最大尺寸,超出此尺寸则清空
|
||||||
if fp != nil {
|
if fp != nil {
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
var totalSize int64 = 0
|
var totalSize int64 = 0
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
SuffixAll = "@GOEDGE_" // 通用后缀
|
||||||
SuffixWebP = "@GOEDGE_WEBP" // WebP后缀
|
SuffixWebP = "@GOEDGE_WEBP" // WebP后缀
|
||||||
SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding
|
SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding
|
||||||
SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod
|
SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod
|
||||||
|
|||||||
11
internal/caches/file_dir.go
Normal file
11
internal/caches/file_dir.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
|
|
||||||
|
type FileDir struct {
|
||||||
|
Path string
|
||||||
|
Capacity *shared.SizeCapacity
|
||||||
|
IsFull bool
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,3 +60,17 @@ func (this *Item) IncreaseHit(week int32) {
|
|||||||
this.Week = week
|
this.Week = week
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Item) RequestURI() string {
|
||||||
|
var schemeIndex = strings.Index(this.Key, "://")
|
||||||
|
if schemeIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstSlashIndex = strings.Index(this.Key[schemeIndex+3:], "/")
|
||||||
|
if firstSlashIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Key[schemeIndex+3+firstSlashIndex:]
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,3 +81,14 @@ func TestItems_Memory2(t *testing.T) {
|
|||||||
t.Log(w, len(i))
|
t.Log(w, len(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestItem_RequestURI(t *testing.T) {
|
||||||
|
for _, u := range []string{
|
||||||
|
"https://goedge.cn/hello/world",
|
||||||
|
"https://goedge.cn:8080/hello/world",
|
||||||
|
"https://goedge.cn/hello/world?v=1&t=123",
|
||||||
|
} {
|
||||||
|
var item = &Item{Key: u}
|
||||||
|
t.Log(u, "=>", item.RequestURI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// TODO 需要优化
|
||||||
this.memoryCache.Clean()
|
this.memoryCache.Clean()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -172,6 +173,46 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
|
||||||
|
func (this *FileList) CleanMatchKey(key string) error {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// TODO 需要优化
|
||||||
|
this.memoryCache.Clean()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
err := db.CleanMatchKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
|
||||||
|
func (this *FileList) CleanMatchPrefix(prefix string) error {
|
||||||
|
if len(prefix) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// TODO 需要优化
|
||||||
|
this.memoryCache.Clean()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
err := db.CleanMatchPrefix(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileList) Remove(hash string) error {
|
func (this *FileList) Remove(hash string) error {
|
||||||
_, err := this.remove(hash)
|
_, err := this.remove(hash)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -10,8 +10,13 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -48,16 +53,16 @@ type FileListDB struct {
|
|||||||
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
||||||
deleteByHashSQL string
|
deleteByHashSQL string
|
||||||
|
|
||||||
statStmt *dbs.Stmt // 统计
|
statStmt *dbs.Stmt // 统计
|
||||||
purgeStmt *dbs.Stmt // 清理
|
purgeStmt *dbs.Stmt // 清理
|
||||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||||
|
updateAccessWeekSQL string // 修改访问日期
|
||||||
|
|
||||||
// hits
|
// hits
|
||||||
insertHitSQL string // 写入数据
|
insertHitSQL string // 写入数据
|
||||||
increaseHitSQL string // 增加点击量
|
increaseHitSQL string // 增加点击量
|
||||||
deleteHitByHashSQL string // 根据hash删除数据
|
deleteHitByHashSQL string // 根据hash删除数据
|
||||||
lfuHitsStmt *dbs.Stmt // 读取老的数据
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileListDB() *FileListDB {
|
func NewFileListDB() *FileListDB {
|
||||||
@@ -84,17 +89,29 @@ func (this *FileListDB) Open(dbPath string) error {
|
|||||||
|
|
||||||
writeDB.SetMaxOpenConns(1)
|
writeDB.SetMaxOpenConns(1)
|
||||||
|
|
||||||
|
this.writeDB = dbs.NewDB(writeDB)
|
||||||
|
|
||||||
// TODO 耗时过长,暂时不整理数据库
|
// TODO 耗时过长,暂时不整理数据库
|
||||||
// TODO 需要根据行数来判断是否VACUUM
|
// TODO 需要根据行数来判断是否VACUUM
|
||||||
|
// TODO 注意VACUUM反而可能让数据库文件变大
|
||||||
/**_, err = db.Exec("VACUUM")
|
/**_, err = db.Exec("VACUUM")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}**/
|
}**/
|
||||||
|
|
||||||
this.writeDB = dbs.NewDB(writeDB)
|
// 检查是否损坏
|
||||||
|
// TODO 暂时屏蔽,因为用时过长
|
||||||
|
|
||||||
|
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||||
|
if len(recoverEnv) > 0 && this.shouldRecover() {
|
||||||
|
for _, indexName := range []string{"staleAt", "hash"} {
|
||||||
|
_, _ = this.writeDB.Exec(`REINDEX "` + indexName + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.writeBatch = dbs.NewBatch(writeDB, 4)
|
this.writeBatch = dbs.NewBatch(writeDB, 4)
|
||||||
this.writeBatch.OnFail(func(err error) {
|
this.writeBatch.OnFail(func(err error) {
|
||||||
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error())
|
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
|
||||||
})
|
})
|
||||||
|
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
@@ -151,7 +168,7 @@ func (this *FileListDB) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt", "accessWeek") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
|
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -163,6 +180,9 @@ func (this *FileListDB) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
|
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
|
this.deleteByHashSQL = `DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`
|
||||||
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)
|
this.deleteByHashStmt, err = this.writeDB.Prepare(this.deleteByHashSQL)
|
||||||
@@ -185,7 +205,12 @@ func (this *FileListDB) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
|
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "accessWeek" ASC, "id" ASC LIMIT ?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
|
||||||
|
|
||||||
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
||||||
|
|
||||||
@@ -193,11 +218,6 @@ func (this *FileListDB) Init() error {
|
|||||||
|
|
||||||
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
||||||
|
|
||||||
this.lfuHitsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.hitsTableName + `" ORDER BY "week" ASC, "week1Hits"+"week2Hits" ASC LIMIT ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isReady = true
|
this.isReady = true
|
||||||
|
|
||||||
// 加载HashMap
|
// 加载HashMap
|
||||||
@@ -226,7 +246,7 @@ func (this *FileListDB) AddAsync(hash string, item *Item) error {
|
|||||||
item.StaleAt = item.ExpiredAt
|
item.StaleAt = item.ExpiredAt
|
||||||
}
|
}
|
||||||
|
|
||||||
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
|
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime(), timeutil.Format("YW"))
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -238,7 +258,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
|||||||
item.StaleAt = item.ExpiredAt
|
item.StaleAt = item.ExpiredAt
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
|
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime(), timeutil.Format("YW"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return this.WrapError(err)
|
return this.WrapError(err)
|
||||||
}
|
}
|
||||||
@@ -300,22 +320,23 @@ func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
|
|||||||
count = 100
|
count = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
hashList, err = this.listLFUItems(count)
|
// 先找过期的
|
||||||
|
hashList, err = this.ListExpiredItems(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var l = len(hashList)
|
||||||
|
|
||||||
if len(hashList) > count/2 {
|
// 从旧缓存中补充
|
||||||
return
|
if l < count {
|
||||||
|
oldHashList, err := this.listOlderItems(count - l)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashList = append(hashList, oldHashList...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不足补齐
|
return hashList, nil
|
||||||
olderHashList, err := this.listOlderItems(count - len(hashList))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hashList = append(hashList, olderHashList...)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
|
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
|
||||||
@@ -342,6 +363,7 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
|
|||||||
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
||||||
var week = timeutil.Format("YW")
|
var week = timeutil.Format("YW")
|
||||||
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
||||||
|
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,6 +394,85 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) CleanMatchKey(key string) error {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略 @GOEDGE_
|
||||||
|
if strings.Contains(key, SuffixAll) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转义
|
||||||
|
var queryKey = strings.ReplaceAll(key, "%", "\\%")
|
||||||
|
queryKey = strings.ReplaceAll(queryKey, "_", "\\_")
|
||||||
|
queryKey = strings.Replace(queryKey, "*", "%", 1)
|
||||||
|
|
||||||
|
// TODO 检查大批量数据下的操作性能
|
||||||
|
var staleLife = 600 // TODO 需要可以设置
|
||||||
|
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
||||||
|
|
||||||
|
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转义
|
||||||
|
var queryPrefix = strings.ReplaceAll(prefix, "%", "\\%")
|
||||||
|
queryPrefix = strings.ReplaceAll(queryPrefix, "_", "\\_")
|
||||||
|
queryPrefix = strings.Replace(queryPrefix, "*", "%", 1)
|
||||||
|
queryPrefix += "%"
|
||||||
|
|
||||||
|
// TODO 检查大批量数据下的操作性能
|
||||||
|
var staleLife = 600 // TODO 需要可以设置
|
||||||
|
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
||||||
|
|
||||||
|
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileListDB) CleanAll() error {
|
func (this *FileListDB) CleanAll() error {
|
||||||
if !this.isReady {
|
if !this.isReady {
|
||||||
return nil
|
return nil
|
||||||
@@ -418,8 +519,9 @@ func (this *FileListDB) Close() error {
|
|||||||
if this.listOlderItemsStmt != nil {
|
if this.listOlderItemsStmt != nil {
|
||||||
_ = this.listOlderItemsStmt.Close()
|
_ = this.listOlderItemsStmt.Close()
|
||||||
}
|
}
|
||||||
if this.lfuHitsStmt != nil {
|
|
||||||
_ = this.lfuHitsStmt.Close()
|
if this.writeBatch != nil {
|
||||||
|
this.writeBatch.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
var errStrings []string
|
var errStrings []string
|
||||||
@@ -438,10 +540,6 @@ func (this *FileListDB) Close() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.writeBatch != nil {
|
|
||||||
this.writeBatch.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errStrings) == 0 {
|
if len(errStrings) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -460,6 +558,7 @@ func (this *FileListDB) initTables(times int) error {
|
|||||||
{
|
{
|
||||||
// expiredAt - 过期时间,用来判断有无过期
|
// expiredAt - 过期时间,用来判断有无过期
|
||||||
// staleAt - 过时缓存最大时间,用来清理缓存
|
// staleAt - 过时缓存最大时间,用来清理缓存
|
||||||
|
// 不对 hash 增加 unique 参数,是尽可能避免产生 malformed 错误
|
||||||
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"hash" varchar(32),
|
"hash" varchar(32),
|
||||||
@@ -472,7 +571,8 @@ func (this *FileListDB) initTables(times int) error {
|
|||||||
"staleAt" integer DEFAULT 0,
|
"staleAt" integer DEFAULT 0,
|
||||||
"createdAt" integer DEFAULT 0,
|
"createdAt" integer DEFAULT 0,
|
||||||
"host" varchar(128),
|
"host" varchar(128),
|
||||||
"serverId" integer
|
"serverId" integer,
|
||||||
|
"accessWeek" varchar(6)
|
||||||
);
|
);
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "createdAt";
|
DROP INDEX IF EXISTS "createdAt";
|
||||||
@@ -484,23 +584,32 @@ ON "` + this.itemsTableName + `" (
|
|||||||
"staleAt" ASC
|
"staleAt" ASC
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
CREATE INDEX IF NOT EXISTS "hash"
|
||||||
ON "` + this.itemsTableName + `" (
|
ON "` + this.itemsTableName + `" (
|
||||||
"hash" ASC
|
"hash" ASC
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 尝试删除重建
|
// 忽略可以预期的错误
|
||||||
if times < 3 {
|
if strings.Contains(err.Error(), "duplicate column name") {
|
||||||
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
err = nil
|
||||||
if dropErr == nil {
|
|
||||||
return this.initTables(times + 1)
|
|
||||||
}
|
|
||||||
return this.WrapError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.WrapError(err)
|
// 尝试删除重建
|
||||||
|
if err != nil {
|
||||||
|
if times < 3 {
|
||||||
|
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
||||||
|
if dropErr == nil {
|
||||||
|
return this.initTables(times + 1)
|
||||||
|
}
|
||||||
|
return this.WrapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.WrapError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,27 +644,6 @@ ON "` + this.hitsTableName + `" (
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileListDB) listLFUItems(count int) (hashList []string, err error) {
|
|
||||||
rows, err := this.lfuHitsStmt.Query(count)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = rows.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var hash string
|
|
||||||
err = rows.Scan(&hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hashList = append(hashList, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
|
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
|
||||||
rows, err := this.listOlderItemsStmt.Query(count)
|
rows, err := this.listOlderItemsStmt.Query(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -576,3 +664,21 @@ func (this *FileListDB) listOlderItems(count int) (hashList []string, err error)
|
|||||||
|
|
||||||
return hashList, nil
|
return hashList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) shouldRecover() bool {
|
||||||
|
result, err := this.writeDB.Query("pragma integrity_check;")
|
||||||
|
if err != nil {
|
||||||
|
logs.Println(result)
|
||||||
|
}
|
||||||
|
var errString = ""
|
||||||
|
var shouldRecover = false
|
||||||
|
for result.Next() {
|
||||||
|
err = result.Scan(&errString)
|
||||||
|
if strings.TrimSpace(errString) != "ok" {
|
||||||
|
shouldRecover = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = result.Close()
|
||||||
|
return shouldRecover
|
||||||
|
}
|
||||||
|
|||||||
87
internal/caches/list_file_db_test.go
Normal file
87
internal/caches/list_file_db_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
_ "github.com/iwind/TeaGo/bootstrap"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
//err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = db.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
hashList, err := db.ListLFUItems(100)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("[", len(hashList), "]", hashList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = db.Init()
|
||||||
|
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// wait transaction
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = db.Init()
|
||||||
|
|
||||||
|
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CleanMatchKey("https://*.goedge.cn:1234/large-text?%2B____")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||||
|
var db = caches.NewFileListDB()
|
||||||
|
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = db.Init()
|
||||||
|
|
||||||
|
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CleanMatchPrefix("https://*.goedge.cn:1234/large-text?%2B____")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,6 +107,12 @@ func (this *FileListHashMap) IsReady() bool {
|
|||||||
return this.isReady
|
return this.isReady
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileListHashMap) Len() int {
|
||||||
|
this.locker.Lock()
|
||||||
|
defer this.locker.Unlock()
|
||||||
|
return len(this.m)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileListHashMap) bigInt(hash string) uint64 {
|
func (this *FileListHashMap) bigInt(hash string) uint64 {
|
||||||
var bigInt = big.NewInt(0)
|
var bigInt = big.NewInt(0)
|
||||||
bigInt.SetString(hash, 16)
|
bigInt.SetString(hash, 16)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFileListHashMap_Memory(t *testing.T) {
|
func TestFileListHashMap_Memory(t *testing.T) {
|
||||||
@@ -68,10 +69,14 @@ func TestFileListHashMap_Load(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var m = caches.NewFileListHashMap()
|
var m = caches.NewFileListHashMap()
|
||||||
err = m.Load(list.GetDB("abc"))
|
var before = time.Now()
|
||||||
|
var db = list.GetDB("abc")
|
||||||
|
err = m.Load(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
t.Log("count:", m.Len())
|
||||||
m.Add("abc")
|
m.Add("abc")
|
||||||
|
|
||||||
for _, hash := range []string{"33347bb4441265405347816cad36a0f8", "a", "abc", "123"} {
|
for _, hash := range []string{"33347bb4441265405347816cad36a0f8", "a", "abc", "123"} {
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ type ListInterface interface {
|
|||||||
// CleanPrefix 清除某个前缀的缓存
|
// CleanPrefix 清除某个前缀的缓存
|
||||||
CleanPrefix(prefix string) error
|
CleanPrefix(prefix string) error
|
||||||
|
|
||||||
|
// CleanMatchKey 清除通配符匹配的Key
|
||||||
|
CleanMatchKey(key string) error
|
||||||
|
|
||||||
|
// CleanMatchPrefix 清除通配符匹配的前缀
|
||||||
|
CleanMatchPrefix(prefix string) error
|
||||||
|
|
||||||
// Remove 删除内容
|
// Remove 删除内容
|
||||||
Remove(hash string) error
|
Remove(hash string) error
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -146,6 +149,82 @@ func (this *MemoryList) CleanPrefix(prefix string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
|
||||||
|
func (this *MemoryList) CleanMatchKey(key string) error {
|
||||||
|
if strings.Contains(key, SuffixAll) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var requestURI = u.RequestURI()
|
||||||
|
|
||||||
|
this.locker.RLock()
|
||||||
|
defer this.locker.RUnlock()
|
||||||
|
|
||||||
|
// TODO 需要优化性能,支持千万级数据低于1s的处理速度
|
||||||
|
for _, itemMap := range this.itemMaps {
|
||||||
|
for _, item := range itemMap {
|
||||||
|
if configutils.MatchDomain(host, item.Host) {
|
||||||
|
var itemRequestURI = item.RequestURI()
|
||||||
|
if itemRequestURI == requestURI || strings.HasPrefix(itemRequestURI, requestURI+SuffixAll) {
|
||||||
|
item.ExpiredAt = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
|
||||||
|
func (this *MemoryList) CleanMatchPrefix(prefix string) error {
|
||||||
|
u, err := url.Parse(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = u.Host
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = hostPart
|
||||||
|
}
|
||||||
|
if len(host) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var requestURI = u.RequestURI()
|
||||||
|
var isRootPath = requestURI == "/"
|
||||||
|
|
||||||
|
this.locker.RLock()
|
||||||
|
defer this.locker.RUnlock()
|
||||||
|
|
||||||
|
// TODO 需要优化性能,支持千万级数据低于1s的处理速度
|
||||||
|
for _, itemMap := range this.itemMaps {
|
||||||
|
for _, item := range itemMap {
|
||||||
|
if configutils.MatchDomain(host, item.Host) {
|
||||||
|
var itemRequestURI = item.RequestURI()
|
||||||
|
if isRootPath || strings.HasPrefix(itemRequestURI, requestURI) {
|
||||||
|
item.ExpiredAt = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (this *MemoryList) Remove(hash string) error {
|
func (this *MemoryList) Remove(hash string) error {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ func init() {
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
// 全局配置
|
// 全局配置
|
||||||
MaxDiskCapacity *shared.SizeCapacity
|
MaxDiskCapacity *shared.SizeCapacity
|
||||||
DiskDir string
|
MainDiskDir string
|
||||||
|
SubDiskDirs []*serverconfigs.CacheDir
|
||||||
MaxMemoryCapacity *shared.SizeCapacity
|
MaxMemoryCapacity *shared.SizeCapacity
|
||||||
|
|
||||||
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
||||||
@@ -47,12 +48,10 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
|||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
newPolicyIds := []int64{}
|
var newPolicyIds = []int64{}
|
||||||
for _, policy := range newPolicies {
|
for _, policy := range newPolicies {
|
||||||
// 使用节点单独的缓存目录
|
// 使用节点单独的缓存目录
|
||||||
if len(this.DiskDir) > 0 {
|
policy.UpdateDiskDir(this.MainDiskDir, this.SubDiskDirs)
|
||||||
policy.UpdateDiskDir(this.DiskDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
newPolicyIds = append(newPolicyIds, policy.Id)
|
newPolicyIds = append(newPolicyIds, policy.Id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type OpenFileCache struct {
|
|||||||
poolList *linkedlist.List
|
poolList *linkedlist.List
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
|
|
||||||
locker sync.Mutex
|
locker sync.RWMutex
|
||||||
|
|
||||||
maxSize int
|
maxSize int
|
||||||
count int
|
count int
|
||||||
@@ -54,13 +54,18 @@ func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *OpenFileCache) Get(filename string) *OpenFile {
|
func (this *OpenFileCache) Get(filename string) *OpenFile {
|
||||||
this.locker.Lock()
|
this.locker.RLock()
|
||||||
defer this.locker.Unlock()
|
|
||||||
pool, ok := this.poolMap[filename]
|
pool, ok := this.poolMap[filename]
|
||||||
|
this.locker.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
file, consumed := pool.Get()
|
file, consumed := pool.Get()
|
||||||
if consumed {
|
if consumed {
|
||||||
|
this.locker.Lock()
|
||||||
this.count--
|
this.count--
|
||||||
|
|
||||||
|
// pool如果为空,也不需要从列表中删除,避免put时需要重新创建
|
||||||
|
|
||||||
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
@@ -124,6 +129,9 @@ func (this *OpenFileCache) Close(filename string) {
|
|||||||
|
|
||||||
pool, ok := this.poolMap[filename]
|
pool, ok := this.poolMap[filename]
|
||||||
if ok {
|
if ok {
|
||||||
|
// 设置关闭状态
|
||||||
|
pool.SetClosing()
|
||||||
|
|
||||||
delete(this.poolMap, filename)
|
delete(this.poolMap, filename)
|
||||||
this.poolList.Remove(pool.linkItem)
|
this.poolList.Remove(pool.linkItem)
|
||||||
_ = this.watcher.Remove(filename)
|
_ = this.watcher.Remove(filename)
|
||||||
@@ -146,6 +154,7 @@ func (this *OpenFileCache) CloseAll() {
|
|||||||
this.poolMap = map[string]*OpenFilePool{}
|
this.poolMap = map[string]*OpenFilePool{}
|
||||||
this.poolList.Reset()
|
this.poolList.Reset()
|
||||||
_ = this.watcher.Close()
|
_ = this.watcher.Close()
|
||||||
|
this.count = 0
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
internal/caches/open_file_cache_test.go
Normal file
43
internal/caches/open_file_cache_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// 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 TestNewOpenFileCache_Close(t *testing.T) {
|
||||||
|
cache, err := caches.NewOpenFileCache(1024)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cache.Debug()
|
||||||
|
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Get("b.txt")
|
||||||
|
cache.Get("d.txt")
|
||||||
|
cache.Close("a.txt")
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewOpenFileCache_CloseAll(t *testing.T) {
|
||||||
|
cache, err := caches.NewOpenFileCache(1024)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cache.Debug()
|
||||||
|
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
cache.Get("b.txt")
|
||||||
|
cache.Get("d.txt")
|
||||||
|
cache.CloseAll()
|
||||||
|
|
||||||
|
time.Sleep(6 * time.Second)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ type OpenFilePool struct {
|
|||||||
linkItem *linkedlist.Item
|
linkItem *linkedlist.Item
|
||||||
filename string
|
filename string
|
||||||
version int64
|
version int64
|
||||||
|
isClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOpenFilePool(filename string) *OpenFilePool {
|
func NewOpenFilePool(filename string) *OpenFilePool {
|
||||||
@@ -29,26 +30,43 @@ func (this *OpenFilePool) Filename() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *OpenFilePool) Get() (*OpenFile, bool) {
|
func (this *OpenFilePool) Get() (*OpenFile, bool) {
|
||||||
|
// 如果已经关闭,直接返回
|
||||||
|
if this.isClosed {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case file := <-this.c:
|
case file := <-this.c:
|
||||||
err := file.SeekStart()
|
if file != nil {
|
||||||
if err != nil {
|
err := file.SeekStart()
|
||||||
_ = file.Close()
|
if err != nil {
|
||||||
return nil, true
|
_ = file.Close()
|
||||||
}
|
return nil, true
|
||||||
file.version = this.version
|
}
|
||||||
|
file.version = this.version
|
||||||
|
|
||||||
return file, true
|
return file, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
default:
|
default:
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *OpenFilePool) Put(file *OpenFile) bool {
|
func (this *OpenFilePool) Put(file *OpenFile) bool {
|
||||||
|
// 如果已关闭,则不接受新的文件
|
||||||
|
if this.isClosed {
|
||||||
|
_ = file.Close()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件版本号
|
||||||
if this.version > 0 && file.version > 0 && file.version != this.version {
|
if this.version > 0 && file.version > 0 && file.version != this.version {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加入Pool
|
||||||
select {
|
select {
|
||||||
case this.c <- file:
|
case this.c <- file:
|
||||||
return true
|
return true
|
||||||
@@ -63,14 +81,18 @@ func (this *OpenFilePool) Len() int {
|
|||||||
return len(this.c)
|
return len(this.c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *OpenFilePool) SetClosing() {
|
||||||
|
this.isClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
func (this *OpenFilePool) Close() {
|
func (this *OpenFilePool) Close() {
|
||||||
Loop:
|
this.isClosed = true
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case file := <-this.c:
|
case file := <-this.c:
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
default:
|
default:
|
||||||
break Loop
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package caches_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,3 +17,30 @@ func TestOpenFilePool_Get(t *testing.T) {
|
|||||||
t.Log(pool.Get())
|
t.Log(pool.Get())
|
||||||
t.Log(pool.Get())
|
t.Log(pool.Get())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenFilePool_Close(t *testing.T) {
|
||||||
|
var pool = caches.NewOpenFilePool("a")
|
||||||
|
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
pool.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenFilePool_Concurrent(t *testing.T) {
|
||||||
|
var pool = caches.NewOpenFilePool("a")
|
||||||
|
var concurrent = 1000
|
||||||
|
var wg = &sync.WaitGroup{}
|
||||||
|
wg.Add(concurrent)
|
||||||
|
for i := 0; i < concurrent; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if rands.Int(0, 1) == 1 {
|
||||||
|
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||||
|
}
|
||||||
|
if rands.Int(0, 1) == 0 {
|
||||||
|
pool.Get()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,38 +3,88 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"github.com/iwind/TeaGo/types"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PartialRanges 内容分区范围定义
|
// PartialRanges 内容分区范围定义
|
||||||
type PartialRanges struct {
|
type PartialRanges struct {
|
||||||
Ranges [][2]int64 `json:"ranges"`
|
Version int `json:"version"` // 版本号
|
||||||
|
Ranges [][2]int64 `json:"ranges"` // 范围
|
||||||
|
BodySize int64 `json:"bodySize"` // 总长度
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialRanges 获取新对象
|
// NewPartialRanges 获取新对象
|
||||||
func NewPartialRanges() *PartialRanges {
|
func NewPartialRanges(expiresAt int64) *PartialRanges {
|
||||||
return &PartialRanges{Ranges: [][2]int64{}}
|
return &PartialRanges{
|
||||||
|
Ranges: [][2]int64{},
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPartialRangesFromData 从数据中解析范围
|
||||||
|
func NewPartialRangesFromData(data []byte) (*PartialRanges, error) {
|
||||||
|
var rs = NewPartialRanges(0)
|
||||||
|
for {
|
||||||
|
var index = bytes.IndexRune(data, '\n')
|
||||||
|
if index < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var line = data[:index]
|
||||||
|
var colonIndex = bytes.IndexRune(line, ':')
|
||||||
|
if colonIndex > 0 {
|
||||||
|
switch string(line[:colonIndex]) {
|
||||||
|
case "v": // 版本号
|
||||||
|
rs.Version = types.Int(line[colonIndex+1:])
|
||||||
|
case "b": // 总长度
|
||||||
|
rs.BodySize = types.Int64(line[colonIndex+1:])
|
||||||
|
case "r": // 范围信息
|
||||||
|
var commaIndex = bytes.IndexRune(line, ',')
|
||||||
|
if commaIndex > 0 {
|
||||||
|
rs.Ranges = append(rs.Ranges, [2]int64{types.Int64(line[colonIndex+1 : commaIndex]), types.Int64(line[commaIndex+1:])})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = data[index+1:]
|
||||||
|
if len(data) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialRangesFromJSON 从JSON中解析范围
|
// NewPartialRangesFromJSON 从JSON中解析范围
|
||||||
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
||||||
var rs = NewPartialRanges()
|
var rs = NewPartialRanges(0)
|
||||||
err := json.Unmarshal(data, &rs)
|
err := json.Unmarshal(data, &rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
rs.Version = 0
|
||||||
|
|
||||||
return rs, nil
|
return rs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPartialRangesFromFile 从文件中加载范围信息
|
||||||
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewPartialRangesFromJSON(data)
|
if len(data) == 0 {
|
||||||
|
return NewPartialRanges(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容老的JSON格式
|
||||||
|
if data[0] == '{' {
|
||||||
|
return NewPartialRangesFromJSON(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新的格式
|
||||||
|
return NewPartialRangesFromData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 添加新范围
|
// Add 添加新范围
|
||||||
@@ -105,29 +155,27 @@ func (this *PartialRanges) Nearest(begin int64, end int64) (r [2]int64, ok bool)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsJSON 转换为JSON
|
// 转换为字符串
|
||||||
func (this *PartialRanges) AsJSON() ([]byte, error) {
|
func (this *PartialRanges) String() string {
|
||||||
return json.Marshal(this)
|
var s = "v:" + strconv.Itoa(this.Version) + "\n" + // version
|
||||||
|
"b:" + this.formatInt64(this.BodySize) + "\n" // bodySize
|
||||||
|
for _, r := range this.Ranges {
|
||||||
|
s += "r:" + this.formatInt64(r[0]) + "," + this.formatInt64(r[1]) + "\n" // range
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes 将内容转换为字节
|
||||||
|
func (this *PartialRanges) Bytes() []byte {
|
||||||
|
return []byte(this.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToFile 写入到文件中
|
// WriteToFile 写入到文件中
|
||||||
func (this *PartialRanges) WriteToFile(path string) error {
|
func (this *PartialRanges) WriteToFile(path string) error {
|
||||||
data, err := this.AsJSON()
|
return os.WriteFile(path, this.Bytes(), 0666)
|
||||||
if err != nil {
|
|
||||||
return errors.New("convert to json failed: " + err.Error())
|
|
||||||
}
|
|
||||||
return os.WriteFile(path, data, 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFromFile 从文件中读取
|
|
||||||
func (this *PartialRanges) ReadFromFile(path string) (*PartialRanges, error) {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewPartialRangesFromJSON(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Max 获取最大位置
|
||||||
func (this *PartialRanges) Max() int64 {
|
func (this *PartialRanges) Max() int64 {
|
||||||
if len(this.Ranges) > 0 {
|
if len(this.Ranges) > 0 {
|
||||||
return this.Ranges[len(this.Ranges)-1][1]
|
return this.Ranges[len(this.Ranges)-1][1]
|
||||||
@@ -135,6 +183,11 @@ func (this *PartialRanges) Max() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset 重置范围信息
|
||||||
|
func (this *PartialRanges) Reset() {
|
||||||
|
this.Ranges = [][2]int64{}
|
||||||
|
}
|
||||||
|
|
||||||
func (this *PartialRanges) merge(index int) {
|
func (this *PartialRanges) merge(index int) {
|
||||||
// forward
|
// forward
|
||||||
var lastIndex = index
|
var lastIndex = index
|
||||||
@@ -187,3 +240,7 @@ func (this *PartialRanges) max(n1 int64, n2 int64) int64 {
|
|||||||
}
|
}
|
||||||
return n2
|
return n2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PartialRanges) formatInt64(i int64) string {
|
||||||
|
return strconv.FormatInt(i, 10)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
package caches_test
|
package caches_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
"github.com/iwind/TeaGo/assert"
|
"github.com/iwind/TeaGo/assert"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewPartialRanges(t *testing.T) {
|
func TestNewPartialRanges(t *testing.T) {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, 100)
|
r.Add(1, 100)
|
||||||
r.Add(50, 300)
|
r.Add(50, 300)
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ func TestNewPartialRanges(t *testing.T) {
|
|||||||
func TestNewPartialRanges1(t *testing.T) {
|
func TestNewPartialRanges1(t *testing.T) {
|
||||||
var a = assert.NewAssertion(t)
|
var a = assert.NewAssertion(t)
|
||||||
|
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, 100)
|
r.Add(1, 100)
|
||||||
r.Add(1, 101)
|
r.Add(1, 101)
|
||||||
r.Add(1, 102)
|
r.Add(1, 102)
|
||||||
@@ -47,7 +49,7 @@ func TestNewPartialRanges1(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewPartialRanges2(t *testing.T) {
|
func TestNewPartialRanges2(t *testing.T) {
|
||||||
// low -> high
|
// low -> high
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, 100)
|
r.Add(1, 100)
|
||||||
r.Add(1, 101)
|
r.Add(1, 101)
|
||||||
r.Add(1, 102)
|
r.Add(1, 102)
|
||||||
@@ -63,7 +65,7 @@ func TestNewPartialRanges2(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewPartialRanges3(t *testing.T) {
|
func TestNewPartialRanges3(t *testing.T) {
|
||||||
// high -> low
|
// high -> low
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 302)
|
r.Add(301, 302)
|
||||||
r.Add(303, 304)
|
r.Add(303, 304)
|
||||||
r.Add(200, 300)
|
r.Add(200, 300)
|
||||||
@@ -75,7 +77,7 @@ func TestNewPartialRanges3(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewPartialRanges4(t *testing.T) {
|
func TestNewPartialRanges4(t *testing.T) {
|
||||||
// nearby
|
// nearby
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 302)
|
r.Add(301, 302)
|
||||||
r.Add(303, 304)
|
r.Add(303, 304)
|
||||||
r.Add(305, 306)
|
r.Add(305, 306)
|
||||||
@@ -90,7 +92,7 @@ func TestNewPartialRanges4(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPartialRanges5(t *testing.T) {
|
func TestNewPartialRanges5(t *testing.T) {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
for j := 0; j < 1000; j++ {
|
for j := 0; j < 1000; j++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Add(int64(j), int64(j+100))
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ func TestNewPartialRanges5(t *testing.T) {
|
|||||||
func TestNewPartialRanges_Nearest(t *testing.T) {
|
func TestNewPartialRanges_Nearest(t *testing.T) {
|
||||||
{
|
{
|
||||||
// nearby
|
// nearby
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 400)
|
r.Add(301, 400)
|
||||||
r.Add(401, 500)
|
r.Add(401, 500)
|
||||||
r.Add(501, 600)
|
r.Add(501, 600)
|
||||||
@@ -112,7 +114,7 @@ func TestNewPartialRanges_Nearest(t *testing.T) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// nearby
|
// nearby
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(301, 400)
|
r.Add(301, 400)
|
||||||
r.Add(450, 500)
|
r.Add(450, 500)
|
||||||
r.Add(550, 600)
|
r.Add(550, 600)
|
||||||
@@ -131,45 +133,100 @@ func TestNewPartialRanges_Large_Range(t *testing.T) {
|
|||||||
var largeSize int64 = 10000000000000
|
var largeSize int64 = 10000000000000
|
||||||
t.Log(largeSize/1024/1024/1024, "G")
|
t.Log(largeSize/1024/1024/1024, "G")
|
||||||
|
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
r.Add(1, largeSize)
|
r.Add(1, largeSize)
|
||||||
jsonData, err := r.AsJSON()
|
var s = r.String()
|
||||||
if err != nil {
|
t.Log(s)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(string(jsonData))
|
|
||||||
|
|
||||||
r2, err := caches.NewPartialRangesFromJSON(jsonData)
|
r2, err := caches.NewPartialRangesFromData([]byte(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.IsTrue(largeSize == r2.Ranges[0][1])
|
a.IsTrue(largeSize == r2.Ranges[0][1])
|
||||||
|
logs.PrintAsJSON(r, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPartialRanges_AsJSON(t *testing.T) {
|
func TestPartialRanges_Encode_JSON(t *testing.T) {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
for j := 0; j < 1000; j++ {
|
for i := 0; i < 10; i++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
|
||||||
}
|
}
|
||||||
data, err := r.AsJSON()
|
var before = time.Now()
|
||||||
|
data, err := json.Marshal(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log(string(data))
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
t.Log(len(data))
|
||||||
|
}
|
||||||
|
|
||||||
r2, err := caches.NewPartialRangesFromJSON(data)
|
func TestPartialRanges_Encode_String(t *testing.T) {
|
||||||
|
var r = caches.NewPartialRanges(0)
|
||||||
|
r.BodySize = 1024
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
|
||||||
|
}
|
||||||
|
var before = time.Now()
|
||||||
|
var data = r.String()
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
t.Log(len(data))
|
||||||
|
|
||||||
|
r2, err := caches.NewPartialRangesFromData([]byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log(r2.Ranges)
|
logs.PrintAsJSON(r2, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialRanges_Version(t *testing.T) {
|
||||||
|
{
|
||||||
|
ranges, err := caches.NewPartialRangesFromData([]byte(`e:1668928495
|
||||||
|
r:0,1048576
|
||||||
|
r:1140260864,1140295164`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("version:", ranges.Version)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ranges, err := caches.NewPartialRangesFromData([]byte(`e:1668928495
|
||||||
|
r:0,1048576
|
||||||
|
r:1140260864,1140295164
|
||||||
|
v:0
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("version:", ranges.Version)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ranges, err := caches.NewPartialRangesFromJSON([]byte(`{}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("version:", ranges.Version)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkNewPartialRanges(b *testing.B) {
|
func BenchmarkNewPartialRanges(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var r = caches.NewPartialRanges()
|
var r = caches.NewPartialRanges(0)
|
||||||
for j := 0; j < 1000; j++ {
|
for j := 0; j < 1000; j++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Add(int64(j), int64(j+100))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkPartialRanges_String(b *testing.B) {
|
||||||
|
var r = caches.NewPartialRanges(0)
|
||||||
|
r.BodySize = 1024
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
r.Ranges = append(r.Ranges, [2]int64{int64(i * 100), int64(i*100 + 100)})
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = r.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
|
|||||||
this.header = this.openFile.header
|
this.header = this.openFile.header
|
||||||
}
|
}
|
||||||
|
|
||||||
isOk := false
|
var isOk = false
|
||||||
|
|
||||||
if autoDiscard {
|
if autoDiscard {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -67,17 +67,17 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
|
|||||||
|
|
||||||
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
|
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
|
||||||
|
|
||||||
status := types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus]))
|
var status = types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus]))
|
||||||
if status < 100 || status > 999 {
|
if status < 100 || status > 999 {
|
||||||
return errors.New("invalid status")
|
return errors.New("invalid status")
|
||||||
}
|
}
|
||||||
this.status = status
|
this.status = status
|
||||||
|
|
||||||
// URL
|
// URL
|
||||||
urlLength := binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength])
|
var urlLength = binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength])
|
||||||
|
|
||||||
// header
|
// header
|
||||||
headerSize := int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength]))
|
var headerSize = int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength]))
|
||||||
if headerSize == 0 {
|
if headerSize == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func (this *FileReader) InitAutoDiscard(autoDiscard bool) error {
|
|||||||
|
|
||||||
// body
|
// body
|
||||||
this.bodyOffset = this.headerOffset + int64(headerSize)
|
this.bodyOffset = this.headerOffset + int64(headerSize)
|
||||||
bodySize := int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength]))
|
var bodySize = int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength]))
|
||||||
if bodySize == 0 {
|
if bodySize == 0 {
|
||||||
isOk = true
|
isOk = true
|
||||||
return nil
|
return nil
|
||||||
@@ -158,7 +158,7 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isOk := false
|
var isOk = false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -171,7 +171,7 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
headerSize := this.headerSize
|
var headerSize = this.headerSize
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err := this.fp.Read(buf)
|
n, err := this.fp.Read(buf)
|
||||||
@@ -215,7 +215,11 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
||||||
isOk := false
|
if this.bodySize == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOk = false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -257,15 +261,22 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) Read(buf []byte) (n int, err error) {
|
func (this *FileReader) Read(buf []byte) (n int, err error) {
|
||||||
|
if this.bodySize == 0 {
|
||||||
|
n = 0
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, err = this.fp.Read(buf)
|
n, err = this.fp.Read(buf)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
_ = this.discard()
|
_ = this.discard()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
||||||
isOk := false
|
var isOk = false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -273,7 +284,7 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
offset := start
|
var offset = start
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
offset = this.bodyOffset + this.bodySize + end
|
offset = this.bodyOffset + this.bodySize + end
|
||||||
end = this.bodyOffset + this.bodySize - 1
|
end = this.bodyOffset + this.bodySize - 1
|
||||||
@@ -296,7 +307,7 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
|
|||||||
for {
|
for {
|
||||||
n, err := this.fp.Read(buf)
|
n, err := this.fp.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
n2 := int(end-offset) + 1
|
var n2 = int(end-offset) + 1
|
||||||
if n2 <= n {
|
if n2 <= n {
|
||||||
_, e := callback(n2)
|
_, e := callback(n2)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@@ -344,12 +355,12 @@ func (this *FileReader) FP() *os.File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) Close() error {
|
func (this *FileReader) Close() error {
|
||||||
if this.openFileCache != nil {
|
if this.isClosed {
|
||||||
if this.isClosed {
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
this.isClosed = true
|
||||||
this.isClosed = true
|
|
||||||
|
|
||||||
|
if this.openFileCache != nil {
|
||||||
if this.openFile != nil {
|
if this.openFile != nil {
|
||||||
this.openFileCache.Put(this.fp.Name(), this.openFile)
|
this.openFileCache.Put(this.fp.Name(), this.openFile)
|
||||||
} else {
|
} else {
|
||||||
@@ -359,6 +370,7 @@ func (this *FileReader) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fp.Close()
|
return this.fp.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestFileReader(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, path := storage.keyPath("my-key")
|
_, path, _ := storage.keyPath("my-key")
|
||||||
|
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -105,7 +105,7 @@ func TestFileReader_Range(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_ = writer.Close()**/
|
_ = writer.Close()**/
|
||||||
|
|
||||||
_, path := storage.keyPath("my-number")
|
_, path, _ := storage.keyPath("my-number")
|
||||||
|
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -117,13 +117,10 @@ func (this *PartialFileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.
|
|||||||
r2, ok = this.ranges.Nearest(r.Start(), r.End())
|
r2, ok = this.ranges.Nearest(r.Start(), r.End())
|
||||||
if ok && this.bodySize > 0 {
|
if ok && this.bodySize > 0 {
|
||||||
// 考虑可配置
|
// 考虑可配置
|
||||||
var span int64 = 512 * 1024
|
const minSpan = 128 << 10
|
||||||
if this.bodySize > 1<<30 {
|
|
||||||
span = 1 << 20
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多
|
// 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多
|
||||||
if r2.Length() < r.Length() && r2.Length() < span {
|
if r2.Length() < r.Length() && r2.Length() < minSpan {
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +135,10 @@ func (this *PartialFileReader) MaxLength() int64 {
|
|||||||
return this.ranges.Max() + 1
|
return this.ranges.Max() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileReader) Ranges() *PartialRanges {
|
||||||
|
return this.ranges
|
||||||
|
}
|
||||||
|
|
||||||
func (this *PartialFileReader) discard() error {
|
func (this *PartialFileReader) discard() error {
|
||||||
_ = os.Remove(this.rangePath)
|
_ = os.Remove(this.rangePath)
|
||||||
return this.FileReader.discard()
|
return this.FileReader.discard()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"golang.org/x/text/message"
|
"golang.org/x/text/message"
|
||||||
"math"
|
"math"
|
||||||
@@ -48,8 +49,7 @@ const (
|
|||||||
SizeBodyLength = 8
|
SizeBodyLength = 8
|
||||||
OffsetBodyLength = OffsetHeaderLength + SizeHeaderLength
|
OffsetBodyLength = OffsetHeaderLength + SizeHeaderLength
|
||||||
|
|
||||||
SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength
|
SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength
|
||||||
OffsetKey = SizeMeta
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -58,6 +58,7 @@ const (
|
|||||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||||
FileTmpSuffix = ".tmp"
|
FileTmpSuffix = ".tmp"
|
||||||
|
MinDiskSpace = 5 << 30 // 当前磁盘最小剩余空间
|
||||||
)
|
)
|
||||||
|
|
||||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||||
@@ -90,6 +91,11 @@ type FileStorage struct {
|
|||||||
ignoreKeys *setutils.FixedSet
|
ignoreKeys *setutils.FixedSet
|
||||||
|
|
||||||
openFileCache *OpenFileCache
|
openFileCache *OpenFileCache
|
||||||
|
|
||||||
|
mainDir string
|
||||||
|
mainDiskIsFull bool
|
||||||
|
|
||||||
|
subDirs []*FileDir
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
||||||
@@ -153,6 +159,16 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subDirs = []*FileDir{}
|
||||||
|
for _, subDir := range newOptions.SubDirs {
|
||||||
|
subDirs = append(subDirs, &FileDir{
|
||||||
|
Path: subDir.Path,
|
||||||
|
Capacity: subDir.Capacity,
|
||||||
|
IsFull: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.checkDiskSpace()
|
||||||
|
|
||||||
err = newOptions.Init()
|
err = newOptions.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error())
|
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error())
|
||||||
@@ -179,7 +195,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
|||||||
// open cache
|
// open cache
|
||||||
oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache)
|
oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache)
|
||||||
newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache)
|
newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache)
|
||||||
if bytes.Compare(oldOpenFileCacheJSON, newOpenFileCacheJSON) != 0 {
|
if !bytes.Equal(oldOpenFileCacheJSON, newOpenFileCacheJSON) {
|
||||||
this.initOpenFileCache()
|
this.initOpenFileCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +231,19 @@ func (this *FileStorage) Init() error {
|
|||||||
this.options.Dir = filepath.Clean(this.options.Dir)
|
this.options.Dir = filepath.Clean(this.options.Dir)
|
||||||
var dir = this.options.Dir
|
var dir = this.options.Dir
|
||||||
|
|
||||||
|
var subDirs = []*FileDir{}
|
||||||
|
for _, subDir := range this.options.SubDirs {
|
||||||
|
subDirs = append(subDirs, &FileDir{
|
||||||
|
Path: subDir.Path,
|
||||||
|
Capacity: subDir.Capacity,
|
||||||
|
IsFull: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.subDirs = subDirs
|
||||||
|
if len(subDirs) > 0 {
|
||||||
|
this.checkDiskSpace()
|
||||||
|
}
|
||||||
|
|
||||||
if len(dir) == 0 {
|
if len(dir) == 0 {
|
||||||
return errors.New("[CACHE]cache storage dir can not be empty")
|
return errors.New("[CACHE]cache storage dir can not be empty")
|
||||||
}
|
}
|
||||||
@@ -287,6 +316,9 @@ func (this *FileStorage) Init() error {
|
|||||||
// open file cache
|
// open file cache
|
||||||
this.initOpenFileCache()
|
this.initOpenFileCache()
|
||||||
|
|
||||||
|
// 检查磁盘空间
|
||||||
|
this.checkDiskSpace()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +346,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, path := this.keyPath(key)
|
hash, path, _ := this.keyPath(key)
|
||||||
|
|
||||||
// 检查文件记录是否已过期
|
// 检查文件记录是否已过期
|
||||||
if !useStale {
|
if !useStale {
|
||||||
@@ -382,16 +414,16 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenWriter 打开缓存文件等待写入
|
// OpenWriter 打开缓存文件等待写入
|
||||||
func (this *FileStorage) OpenWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) {
|
func (this *FileStorage) OpenWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||||
return this.openWriter(key, expiresAt, status, size, maxSize, isPartial, false)
|
return this.openWriter(key, expiresAt, status, headerSize, bodySize, maxSize, isPartial, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
||||||
func (this *FileStorage) OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) {
|
func (this *FileStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
|
||||||
return this.openWriter(key, expiresAt, status, -1, -1, false, true)
|
return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileStorage) openWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool, isFlushing bool) (Writer, error) {
|
func (this *FileStorage) openWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool, isFlushing bool) (Writer, error) {
|
||||||
// 是否正在退出
|
// 是否正在退出
|
||||||
if teaconst.IsQuiting {
|
if teaconst.IsQuiting {
|
||||||
return nil, ErrWritingUnavailable
|
return nil, ErrWritingUnavailable
|
||||||
@@ -409,8 +441,8 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
maxMemorySize = maxSize
|
maxMemorySize = maxSize
|
||||||
}
|
}
|
||||||
var memoryStorage = this.memoryStorage
|
var memoryStorage = this.memoryStorage
|
||||||
if !isFlushing && !isPartial && memoryStorage != nil && ((size > 0 && size < maxMemorySize) || size < 0) {
|
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, size, maxMemorySize, false)
|
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return writer, nil
|
return writer, nil
|
||||||
}
|
}
|
||||||
@@ -463,17 +495,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
|
|
||||||
var hash = stringutil.Md5(key)
|
var hash = stringutil.Md5(key)
|
||||||
|
|
||||||
// TODO 可以只stat一次
|
dir, diskIsFull := this.subDir(hash)
|
||||||
var dir = this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
if diskIsFull {
|
||||||
_, err = os.Stat(dir)
|
return nil, NewCapacityError("the disk is full")
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = os.MkdirAll(dir, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查缓存是否已经生成
|
// 检查缓存是否已经生成
|
||||||
@@ -520,19 +544,38 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
// 从已经存储的内容中读取信息
|
// 从已经存储的内容中读取信息
|
||||||
var isNewCreated = true
|
var isNewCreated = true
|
||||||
var partialBodyOffset int64
|
var partialBodyOffset int64
|
||||||
|
var partialRanges *PartialRanges
|
||||||
if isPartial {
|
if isPartial {
|
||||||
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
// 数据库中是否存在
|
||||||
if err == nil {
|
existsCacheItem, _ := this.list.Exist(hash)
|
||||||
var partialReader = NewPartialFileReader(readerFp)
|
if existsCacheItem {
|
||||||
err = partialReader.Init()
|
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
||||||
_ = partialReader.Close()
|
if err == nil {
|
||||||
if err == nil && partialReader.bodyOffset > 0 {
|
var partialReader = NewPartialFileReader(readerFp)
|
||||||
isNewCreated = false
|
err = partialReader.Init()
|
||||||
partialBodyOffset = partialReader.bodyOffset
|
_ = partialReader.Close()
|
||||||
} else {
|
if err == nil && partialReader.bodyOffset > 0 {
|
||||||
_ = this.removeCacheFile(tmpPath)
|
partialRanges = partialReader.Ranges()
|
||||||
|
if bodySize > 0 && partialRanges != nil && partialRanges.BodySize > 0 && bodySize != partialRanges.BodySize {
|
||||||
|
_ = this.removeCacheFile(tmpPath)
|
||||||
|
} else {
|
||||||
|
isNewCreated = false
|
||||||
|
partialBodyOffset = partialReader.bodyOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = this.removeCacheFile(tmpPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isNewCreated {
|
||||||
|
err = this.list.Remove(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if partialRanges == nil {
|
||||||
|
partialRanges = NewPartialRanges(expiredAt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = os.O_CREATE | os.O_WRONLY
|
var flags = os.O_CREATE | os.O_WRONLY
|
||||||
@@ -542,7 +585,16 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
var before = time.Now()
|
var before = time.Now()
|
||||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// TODO 检查在各个系统中的稳定性
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
_ = os.MkdirAll(dir, 0777)
|
||||||
|
|
||||||
|
// open file again
|
||||||
|
writer, err = os.OpenFile(tmpPath, flags, 0666)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !isFlushing {
|
if !isFlushing {
|
||||||
if time.Since(before) >= maxOpenFilesSlowCost {
|
if time.Since(before) >= maxOpenFilesSlowCost {
|
||||||
@@ -574,9 +626,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var metaBodySize int64 = -1
|
||||||
|
var metaHeaderSize = -1
|
||||||
if isNewCreated {
|
if isNewCreated {
|
||||||
// 写入过期时间
|
// 写入meta
|
||||||
var metaBytes = make([]byte, SizeMeta+len(key))
|
// 从v0.5.8开始不再在meta中写入Key
|
||||||
|
var metaBytes = make([]byte, SizeMeta)
|
||||||
binary.BigEndian.PutUint32(metaBytes[OffsetExpiresAt:], uint32(expiredAt))
|
binary.BigEndian.PutUint32(metaBytes[OffsetExpiresAt:], uint32(expiredAt))
|
||||||
|
|
||||||
// 写入状态码
|
// 写入状态码
|
||||||
@@ -585,17 +640,17 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
copy(metaBytes[OffsetStatus:], strconv.Itoa(status))
|
copy(metaBytes[OffsetStatus:], strconv.Itoa(status))
|
||||||
|
|
||||||
// 写入URL长度
|
|
||||||
binary.BigEndian.PutUint32(metaBytes[OffsetURLLength:], uint32(len(key)))
|
|
||||||
|
|
||||||
// 写入Header Length
|
// 写入Header Length
|
||||||
binary.BigEndian.PutUint32(metaBytes[OffsetHeaderLength:], uint32(0))
|
if headerSize > 0 {
|
||||||
|
binary.BigEndian.PutUint32(metaBytes[OffsetHeaderLength:], uint32(headerSize))
|
||||||
|
metaHeaderSize = headerSize
|
||||||
|
}
|
||||||
|
|
||||||
// 写入Body Length
|
// 写入Body Length
|
||||||
binary.BigEndian.PutUint64(metaBytes[OffsetBodyLength:], uint64(0))
|
if bodySize > 0 {
|
||||||
|
binary.BigEndian.PutUint64(metaBytes[OffsetBodyLength:], uint64(bodySize))
|
||||||
// 写入URL
|
metaBodySize = bodySize
|
||||||
copy(metaBytes[OffsetKey:], key)
|
}
|
||||||
|
|
||||||
_, err = writer.Write(metaBytes)
|
_, err = writer.Write(metaBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -605,12 +660,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
|
|
||||||
isOk = true
|
isOk = true
|
||||||
if isPartial {
|
if isPartial {
|
||||||
ranges, err := NewPartialRangesFromFile(cachePathName + "@ranges.cache")
|
return NewPartialFileWriter(writer, key, expiredAt, metaHeaderSize, metaBodySize, isNewCreated, isPartial, partialBodyOffset, partialRanges, func() {
|
||||||
if err != nil {
|
|
||||||
ranges = NewPartialRanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewPartialFileWriter(writer, key, expiredAt, isNewCreated, isPartial, partialBodyOffset, ranges, func() {
|
|
||||||
sharedWritingFileKeyLocker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(sharedWritingFileKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
if len(sharedWritingFileKeyMap) == 0 {
|
if len(sharedWritingFileKeyMap) == 0 {
|
||||||
@@ -619,7 +669,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, siz
|
|||||||
sharedWritingFileKeyLocker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
}), nil
|
}), nil
|
||||||
} else {
|
} else {
|
||||||
return NewFileWriter(this, writer, key, expiredAt, -1, func() {
|
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, -1, func() {
|
||||||
sharedWritingFileKeyLocker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(sharedWritingFileKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
if len(sharedWritingFileKeyMap) == 0 {
|
if len(sharedWritingFileKeyMap) == 0 {
|
||||||
@@ -646,7 +696,7 @@ func (this *FileStorage) AddToList(item *Item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.MetaSize = SizeMeta + 128
|
item.MetaSize = SizeMeta + 128
|
||||||
hash := stringutil.Md5(item.Key)
|
var hash = stringutil.Md5(item.Key)
|
||||||
err := this.list.Add(hash, item)
|
err := this.list.Add(hash, item)
|
||||||
if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||||
remotelogs.Error("CACHE", "add to list failed: "+err.Error())
|
remotelogs.Error("CACHE", "add to list failed: "+err.Error())
|
||||||
@@ -660,15 +710,12 @@ func (this *FileStorage) Delete(key string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
|
||||||
defer this.locker.Unlock()
|
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = memoryStorage.Delete(key)
|
_ = memoryStorage.Delete(key)
|
||||||
})
|
})
|
||||||
|
|
||||||
hash, path := this.keyPath(key)
|
hash, path, _ := this.keyPath(key)
|
||||||
err := this.list.Remove(hash)
|
err := this.list.Remove(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -683,9 +730,6 @@ func (this *FileStorage) Delete(key string) error {
|
|||||||
|
|
||||||
// Stat 统计
|
// Stat 统计
|
||||||
func (this *FileStorage) Stat() (*Stat, error) {
|
func (this *FileStorage) Stat() (*Stat, error) {
|
||||||
this.locker.RLock()
|
|
||||||
defer this.locker.RUnlock()
|
|
||||||
|
|
||||||
return this.list.Stat(func(hash string) bool {
|
return this.list.Stat(func(hash string) bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -708,57 +752,72 @@ func (this *FileStorage) CleanAll() error {
|
|||||||
|
|
||||||
// 删除缓存和目录
|
// 删除缓存和目录
|
||||||
// 不能直接删除子目录,比较危险
|
// 不能直接删除子目录,比较危险
|
||||||
dir := this.dir()
|
|
||||||
fp, err := os.Open(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = fp.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
stat, err := fp.Stat()
|
var rootDirs = []string{this.options.Dir}
|
||||||
if err != nil {
|
var subDirs = this.subDirs // copy slice
|
||||||
return err
|
if len(subDirs) > 0 {
|
||||||
}
|
for _, subDir := range subDirs {
|
||||||
|
rootDirs = append(rootDirs, subDir.Path)
|
||||||
if !stat.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 改成待删除
|
|
||||||
subDirs, err := fp.Readdir(-1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, info := range subDirs {
|
|
||||||
subDir := info.Name()
|
|
||||||
|
|
||||||
// 检查目录名
|
|
||||||
ok, err := regexp.MatchString(`^[0-9a-f]{2}$`, subDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 修改目录名
|
var dirNameReg = regexp.MustCompile(`^[0-9a-f]{2}$`)
|
||||||
tmpDir := dir + "/" + subDir + "-deleted"
|
for _, rootDir := range rootDirs {
|
||||||
err = os.Rename(dir+"/"+subDir, tmpDir)
|
var dir = rootDir + "/p" + types.String(this.policy.Id)
|
||||||
|
err = func(dir string) error {
|
||||||
|
fp, err := os.Open(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stat, err := fp.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改成待删除
|
||||||
|
subDirs, err := fp.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, info := range subDirs {
|
||||||
|
subDir := info.Name()
|
||||||
|
|
||||||
|
// 检查目录名
|
||||||
|
if !dirNameReg.MatchString(subDir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改目录名
|
||||||
|
tmpDir := dir + "/" + subDir + "-deleted"
|
||||||
|
err = os.Rename(dir+"/"+subDir, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新遍历待删除
|
||||||
|
goman.New(func() {
|
||||||
|
err = this.cleanDeletedDirs(dir)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新遍历待删除
|
|
||||||
goman.New(func() {
|
|
||||||
err = this.cleanDeletedDirs(dir)
|
|
||||||
if err != nil {
|
|
||||||
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,9 +828,6 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
|
||||||
defer this.locker.Unlock()
|
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = memoryStorage.Purge(keys, urlType)
|
_ = memoryStorage.Purge(keys, urlType)
|
||||||
@@ -780,6 +836,19 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
// 目录
|
// 目录
|
||||||
if urlType == "dir" {
|
if urlType == "dir" {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchPrefix(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := this.list.CleanPrefix(key)
|
err := this.list.CleanPrefix(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -790,7 +859,21 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
|
|
||||||
// URL
|
// URL
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
hash, path := this.keyPath(key)
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通的Key
|
||||||
|
hash, path, _ := this.keyPath(key)
|
||||||
err := this.removeCacheFile(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
@@ -861,25 +944,22 @@ func (this *FileStorage) CanSendfile() bool {
|
|||||||
return this.options.EnableSendfile
|
return this.options.EnableSendfile
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绝对路径
|
|
||||||
func (this *FileStorage) dir() string {
|
|
||||||
return this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取Key对应的文件路径
|
// 获取Key对应的文件路径
|
||||||
func (this *FileStorage) keyPath(key string) (hash string, path string) {
|
func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFull bool) {
|
||||||
hash = stringutil.Md5(key)
|
hash = stringutil.Md5(key)
|
||||||
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
var dir string
|
||||||
|
dir, diskIsFull = this.subDir(hash)
|
||||||
path = dir + "/" + hash + ".cache"
|
path = dir + "/" + hash + ".cache"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Hash对应的文件路径
|
// 获取Hash对应的文件路径
|
||||||
func (this *FileStorage) hashPath(hash string) (path string) {
|
func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
|
||||||
if len(hash) != 32 {
|
if len(hash) != 32 {
|
||||||
return ""
|
return "", false
|
||||||
}
|
}
|
||||||
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
var dir string
|
||||||
|
dir, diskIsFull = this.subDir(hash)
|
||||||
path = dir + "/" + hash + ".cache"
|
path = dir + "/" + hash + ".cache"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -937,19 +1017,39 @@ func (this *FileStorage) initList() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 清理任务
|
// 清理任务
|
||||||
|
// TODO purge每个分区
|
||||||
func (this *FileStorage) purgeLoop() {
|
func (this *FileStorage) purgeLoop() {
|
||||||
|
// 检查磁盘剩余空间
|
||||||
|
this.checkDiskSpace()
|
||||||
|
|
||||||
// 计算是否应该开启LFU清理
|
// 计算是否应该开启LFU清理
|
||||||
var capacityBytes = this.policy.CapacityBytes()
|
var capacityBytes = this.diskCapacityBytes()
|
||||||
var startLFU = false
|
var startLFU = false
|
||||||
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
|
|
||||||
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
||||||
if lfuFreePercent <= 0 {
|
if lfuFreePercent <= 0 {
|
||||||
lfuFreePercent = 5
|
lfuFreePercent = 5
|
||||||
}
|
}
|
||||||
if capacityBytes > 0 {
|
|
||||||
if lfuFreePercent < 100 {
|
var hasFullDisk = this.mainDiskIsFull
|
||||||
if usedPercent >= 100-lfuFreePercent {
|
if !hasFullDisk {
|
||||||
startLFU = true
|
var subDirs = this.subDirs // copy slice
|
||||||
|
for _, subDir := range subDirs {
|
||||||
|
if subDir.IsFull {
|
||||||
|
hasFullDisk = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFullDisk {
|
||||||
|
startLFU = true
|
||||||
|
} else {
|
||||||
|
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
|
||||||
|
if capacityBytes > 0 {
|
||||||
|
if lfuFreePercent < 100 {
|
||||||
|
if usedPercent >= 100-lfuFreePercent {
|
||||||
|
startLFU = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,7 +1074,7 @@ func (this *FileStorage) purgeLoop() {
|
|||||||
}
|
}
|
||||||
for i := 0; i < times; i++ {
|
for i := 0; i < times; i++ {
|
||||||
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
||||||
path := this.hashPath(hash)
|
path, _ := this.hashPath(hash)
|
||||||
err := this.removeCacheFile(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||||
@@ -1008,7 +1108,7 @@ func (this *FileStorage) purgeLoop() {
|
|||||||
|
|
||||||
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
||||||
err := this.list.PurgeLFU(count, func(hash string) error {
|
err := this.list.PurgeLFU(count, func(hash string) error {
|
||||||
path := this.hashPath(hash)
|
path, _ := this.hashPath(hash)
|
||||||
err := this.removeCacheFile(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||||
@@ -1089,7 +1189,7 @@ func (this *FileStorage) hotLoop() {
|
|||||||
expiresAt = bestExpiresAt
|
expiresAt = bestExpiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), reader.BodySize(), -1, false)
|
writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), types.Int(reader.HeaderSize()), reader.BodySize(), -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !CanIgnoreErr(err) {
|
if !CanIgnoreErr(err) {
|
||||||
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
||||||
@@ -1113,9 +1213,12 @@ func (this *FileStorage) hotLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
||||||
_, err = writer.Write(buf[:n])
|
goNext = true
|
||||||
if err == nil {
|
if n > 0 {
|
||||||
goNext = true
|
_, err = writer.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
goNext = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
@@ -1128,6 +1231,7 @@ func (this *FileStorage) hotLoop() {
|
|||||||
memoryStorage.AddToList(&Item{
|
memoryStorage.AddToList(&Item{
|
||||||
Type: writer.ItemType(),
|
Type: writer.ItemType(),
|
||||||
Key: item.Key,
|
Key: item.Key,
|
||||||
|
Host: ParseHost(item.Key),
|
||||||
ExpiredAt: expiresAt,
|
ExpiredAt: expiresAt,
|
||||||
HeaderSize: writer.HeaderSize(),
|
HeaderSize: writer.HeaderSize(),
|
||||||
BodySize: writer.BodySize(),
|
BodySize: writer.BodySize(),
|
||||||
@@ -1327,3 +1431,63 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
|||||||
f(memoryStorage)
|
f(memoryStorage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查磁盘剩余空间
|
||||||
|
func (this *FileStorage) checkDiskSpace() {
|
||||||
|
if this.options != nil && len(this.options.Dir) > 0 {
|
||||||
|
var stat unix.Statfs_t
|
||||||
|
err := unix.Statfs(this.options.Dir, &stat)
|
||||||
|
if err == nil {
|
||||||
|
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
||||||
|
this.mainDiskIsFull = availableBytes < MinDiskSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var subDirs = this.subDirs // copy slice
|
||||||
|
for _, subDir := range subDirs {
|
||||||
|
var stat unix.Statfs_t
|
||||||
|
err := unix.Statfs(subDir.Path, &stat)
|
||||||
|
if err == nil {
|
||||||
|
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
||||||
|
subDir.IsFull = availableBytes < MinDiskSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取目录
|
||||||
|
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||||
|
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
|
||||||
|
|
||||||
|
if len(hash) < 4 {
|
||||||
|
return this.options.Dir + suffix, this.mainDiskIsFull
|
||||||
|
}
|
||||||
|
|
||||||
|
var subDirs = this.subDirs // copy slice
|
||||||
|
var countSubDirs = len(subDirs)
|
||||||
|
if countSubDirs == 0 {
|
||||||
|
return this.options.Dir + suffix, this.mainDiskIsFull
|
||||||
|
}
|
||||||
|
|
||||||
|
countSubDirs++ // add main dir
|
||||||
|
|
||||||
|
// 最多只支持16个目录
|
||||||
|
if countSubDirs > 16 {
|
||||||
|
countSubDirs = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirIndex = this.charCode(hash[0]) % uint8(countSubDirs)
|
||||||
|
if dirIndex == 0 {
|
||||||
|
return this.options.Dir + suffix, this.mainDiskIsFull
|
||||||
|
}
|
||||||
|
var subDir = subDirs[dirIndex-1]
|
||||||
|
return subDir.Path + suffix, subDir.IsFull
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileStorage) charCode(r byte) uint8 {
|
||||||
|
if r >= '0' && r <= '9' {
|
||||||
|
return r - '0'
|
||||||
|
}
|
||||||
|
if r >= 'a' && r <= 'z' {
|
||||||
|
return r - 'a' + 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestFileStorage_OpenWriter(t *testing.T) {
|
|||||||
|
|
||||||
header := []byte("Header")
|
header := []byte("Header")
|
||||||
body := []byte("This is Body")
|
body := []byte("This is Body")
|
||||||
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, true)
|
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, -1, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
|||||||
t.Log(time.Since(now).Seconds()*1000, "ms")
|
t.Log(time.Since(now).Seconds()*1000, "ms")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
|||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrFileIsWriting {
|
if err != ErrFileIsWriting {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -267,7 +267,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
|||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrFileIsWriting {
|
if err != ErrFileIsWriting {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -522,7 +522,7 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, path := storage.keyPath("my-key")
|
_, path, _ := storage.keyPath("my-key")
|
||||||
t.Log(path)
|
t.Log(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,6 +569,6 @@ func BenchmarkFileStorage_KeyPath(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = storage.keyPath(strconv.Itoa(i))
|
_, _, _ = storage.keyPath(strconv.Itoa(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ type StorageInterface interface {
|
|||||||
|
|
||||||
// OpenWriter 打开缓存写入器等待写入
|
// OpenWriter 打开缓存写入器等待写入
|
||||||
// size 和 maxSize 可能为-1
|
// size 和 maxSize 可能为-1
|
||||||
OpenWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error)
|
OpenWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error)
|
||||||
|
|
||||||
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
||||||
OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error)
|
OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error)
|
||||||
|
|
||||||
// Delete 删除某个键值对应的缓存
|
// Delete 删除某个键值对应的缓存
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
@@ -17,6 +16,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -149,7 +149,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenWriter 打开缓存写入器等待写入
|
// OpenWriter 打开缓存写入器等待写入
|
||||||
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) {
|
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||||
if this.ignoreKeys.Has(key) {
|
if this.ignoreKeys.Has(key) {
|
||||||
return nil, ErrEntityTooLarge
|
return nil, ErrEntityTooLarge
|
||||||
}
|
}
|
||||||
@@ -158,15 +158,15 @@ func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, s
|
|||||||
if isPartial {
|
if isPartial {
|
||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
return this.openWriter(key, expiredAt, status, size, maxSize, true)
|
return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
// OpenFlushWriter 打开从其他媒介直接刷入的写入器
|
||||||
func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int) (Writer, error) {
|
func (this *MemoryStorage) OpenFlushWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64) (Writer, error) {
|
||||||
return this.openWriter(key, expiresAt, status, -1, -1, true)
|
return this.openWriter(key, expiresAt, status, headerSize, bodySize, -1, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, size int64, maxSize int64, isDirty bool) (Writer, error) {
|
func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, headerSize int, bodySize int64, maxSize int64, isDirty bool) (Writer, error) {
|
||||||
// 待写入队列是否已满
|
// 待写入队列是否已满
|
||||||
if isDirty &&
|
if isDirty &&
|
||||||
this.parentStorage != nil &&
|
this.parentStorage != nil &&
|
||||||
@@ -207,10 +207,10 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, s
|
|||||||
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
|
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
|
||||||
}
|
}
|
||||||
capacityBytes := this.memoryCapacityBytes()
|
capacityBytes := this.memoryCapacityBytes()
|
||||||
if size < 0 {
|
if bodySize < 0 {
|
||||||
size = 0
|
bodySize = 0
|
||||||
}
|
}
|
||||||
if capacityBytes > 0 && capacityBytes <= this.totalSize+size {
|
if capacityBytes > 0 && capacityBytes <= this.totalSize+bodySize {
|
||||||
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
|
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,10 +230,10 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, s
|
|||||||
|
|
||||||
// Delete 删除某个键值对应的缓存
|
// Delete 删除某个键值对应的缓存
|
||||||
func (this *MemoryStorage) Delete(key string) error {
|
func (this *MemoryStorage) Delete(key string) error {
|
||||||
hash := this.hash(key)
|
var hash = this.hash(key)
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
delete(this.valuesMap, hash)
|
delete(this.valuesMap, hash)
|
||||||
_ = this.list.Remove(fmt.Sprintf("%d", hash))
|
_ = this.list.Remove(types.String(hash))
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -263,6 +263,19 @@ func (this *MemoryStorage) Purge(keys []string, urlType string) error {
|
|||||||
// 目录
|
// 目录
|
||||||
if urlType == "dir" {
|
if urlType == "dir" {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchPrefix(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := this.list.CleanPrefix(key)
|
err := this.list.CleanPrefix(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -273,6 +286,19 @@ func (this *MemoryStorage) Purge(keys []string, urlType string) error {
|
|||||||
|
|
||||||
// URL
|
// URL
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
// 检查是否有通配符 http(s)://*.example.com
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex > 0 {
|
||||||
|
var keyRight = key[schemeIndex+3:]
|
||||||
|
if strings.HasPrefix(keyRight, "*.") {
|
||||||
|
err := this.list.CleanMatchKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := this.Delete(key)
|
err := this.Delete(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -336,7 +362,12 @@ func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePol
|
|||||||
// AddToList 将缓存添加到列表
|
// AddToList 将缓存添加到列表
|
||||||
func (this *MemoryStorage) AddToList(item *Item) {
|
func (this *MemoryStorage) AddToList(item *Item) {
|
||||||
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
||||||
hash := fmt.Sprintf("%d", this.hash(item.Key))
|
var hash = types.String(this.hash(item.Key))
|
||||||
|
|
||||||
|
if len(item.Host) == 0 {
|
||||||
|
item.Host = ParseHost(item.Key)
|
||||||
|
}
|
||||||
|
|
||||||
_ = this.list.Add(hash, item)
|
_ = this.list.Add(hash, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +464,7 @@ func (this *MemoryStorage) startFlush() {
|
|||||||
var statCount = 0
|
var statCount = 0
|
||||||
var writeDelayMS float64 = 0
|
var writeDelayMS float64 = 0
|
||||||
|
|
||||||
for hash := range this.dirtyChan {
|
for key := range this.dirtyChan {
|
||||||
statCount++
|
statCount++
|
||||||
|
|
||||||
if statCount == 100 {
|
if statCount == 100 {
|
||||||
@@ -455,7 +486,7 @@ func (this *MemoryStorage) startFlush() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.flushItem(hash)
|
this.flushItem(key)
|
||||||
|
|
||||||
if writeDelayMS > 0 {
|
if writeDelayMS > 0 {
|
||||||
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
||||||
@@ -481,7 +512,7 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status)
|
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !CanIgnoreErr(err) {
|
if !CanIgnoreErr(err) {
|
||||||
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
||||||
@@ -513,6 +544,7 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
this.parentStorage.AddToList(&Item{
|
this.parentStorage.AddToList(&Item{
|
||||||
Type: writer.ItemType(),
|
Type: writer.ItemType(),
|
||||||
Key: key,
|
Key: key,
|
||||||
|
Host: ParseHost(key),
|
||||||
ExpiredAt: item.ExpiresAt,
|
ExpiredAt: item.ExpiresAt,
|
||||||
HeaderSize: writer.HeaderSize(),
|
HeaderSize: writer.HeaderSize(),
|
||||||
BodySize: writer.BodySize(),
|
BodySize: writer.BodySize(),
|
||||||
@@ -520,8 +552,6 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
|
|
||||||
// 从内存中移除
|
// 从内存中移除
|
||||||
_ = this.Delete(key)
|
_ = this.Delete(key)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
||||||
@@ -544,7 +574,7 @@ func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
|||||||
func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
||||||
hash := this.hash(key)
|
hash := this.hash(key)
|
||||||
delete(this.valuesMap, hash)
|
delete(this.valuesMap, hash)
|
||||||
_ = this.list.Remove(fmt.Sprintf("%d", hash))
|
_ = this.list.Remove(types.String(hash))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,15 +14,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.WriteHeader([]byte("Header"))
|
_, _ = writer.WriteHeader([]byte("Header"))
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
_, _ = writer.Write([]byte(", World"))
|
_, _ = writer.Write([]byte(", World"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(storage.valuesMap)
|
t.Log(storage.valuesMap)
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -30,6 +37,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc")
|
t.Log("not found: abc")
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -63,7 +71,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -102,21 +110,29 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Delete(t *testing.T) {
|
func TestMemoryStorage_Delete(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
}
|
}
|
||||||
_ = storage.Delete("abc1")
|
_ = storage.Delete("abc1")
|
||||||
@@ -124,14 +140,18 @@ func TestMemoryStorage_Delete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Stat(t *testing.T) {
|
func TestMemoryStorage_Stat(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc",
|
Key: "abc",
|
||||||
@@ -140,11 +160,15 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc1",
|
Key: "abc1",
|
||||||
@@ -161,14 +185,18 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_CleanAll(t *testing.T) {
|
func TestMemoryStorage_CleanAll(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
var expiredAt = time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc",
|
Key: "abc",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -176,11 +204,15 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc1",
|
Key: "abc1",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -199,11 +231,15 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc",
|
Key: "abc",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -211,11 +247,15 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: "abc1",
|
Key: "abc1",
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -231,7 +271,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Expire(t *testing.T) {
|
func TestMemoryStorage_Expire(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
MemoryAutoPurgeInterval: 5,
|
MemoryAutoPurgeInterval: 5,
|
||||||
}, nil)
|
}, nil)
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
@@ -242,11 +282,15 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
|||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
|
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
|
||||||
key := "abc" + strconv.Itoa(i)
|
key := "abc" + strconv.Itoa(i)
|
||||||
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, false)
|
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello"))
|
_, _ = writer.Write([]byte("Hello"))
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
storage.AddToList(&Item{
|
storage.AddToList(&Item{
|
||||||
Key: key,
|
Key: key,
|
||||||
BodySize: 5,
|
BodySize: 5,
|
||||||
@@ -257,7 +301,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Locker(t *testing.T) {
|
func TestMemoryStorage_Locker(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
err := storage.Init()
|
err := storage.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
30
internal/caches/utils.go
Normal file
30
internal/caches/utils.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseHost(key string) string {
|
||||||
|
var schemeIndex = strings.Index(key, "://")
|
||||||
|
if schemeIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstSlashIndex = strings.Index(key[schemeIndex+3:], "/")
|
||||||
|
if firstSlashIndex <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = key[schemeIndex+3 : schemeIndex+3+firstSlashIndex]
|
||||||
|
|
||||||
|
hostPart, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil && len(hostPart) > 0 {
|
||||||
|
host = configutils.QuoteIP(hostPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
return host
|
||||||
|
}
|
||||||
51
internal/caches/utils_test.go
Normal file
51
internal/caches/utils_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package caches_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/cespare/xxhash"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseHost(t *testing.T) {
|
||||||
|
for _, u := range []string{
|
||||||
|
"https://goedge.cn/hello/world",
|
||||||
|
"https://goedge.cn:8080/hello/world",
|
||||||
|
"https://goedge.cn/hello/world?v=1&t=123",
|
||||||
|
"https://[::1]:1234/hello/world?v=1&t=123",
|
||||||
|
"https://[::1]/hello/world?v=1&t=123",
|
||||||
|
"https://127.0.0.1/hello/world?v=1&t=123",
|
||||||
|
"https:/hello/world?v=1&t=123",
|
||||||
|
"123456",
|
||||||
|
} {
|
||||||
|
t.Log(u, "=>", caches.ParseHost(u))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUintString(t *testing.T) {
|
||||||
|
t.Log(strconv.FormatUint(xxhash.Sum64String("https://goedge.cn/"), 10))
|
||||||
|
t.Log(strconv.FormatUint(123456789, 10))
|
||||||
|
t.Log(fmt.Sprintf("%d", 1234567890123))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUint_String(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = strconv.FormatUint(1234567890123, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUint_String2(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = types.String(1234567890123)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUint_String3(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = fmt.Sprintf("%d", 1234567890123)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,25 +11,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FileWriter struct {
|
type FileWriter struct {
|
||||||
storage StorageInterface
|
storage StorageInterface
|
||||||
rawWriter *os.File
|
rawWriter *os.File
|
||||||
key string
|
key string
|
||||||
headerSize int64
|
|
||||||
bodySize int64
|
metaHeaderSize int
|
||||||
expiredAt int64
|
headerSize int64
|
||||||
maxSize int64
|
|
||||||
endFunc func()
|
metaBodySize int64 // 写入前的内容长度
|
||||||
once sync.Once
|
bodySize int64
|
||||||
|
|
||||||
|
expiredAt int64
|
||||||
|
maxSize int64
|
||||||
|
endFunc func()
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, maxSize int64, endFunc func()) *FileWriter {
|
func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, metaHeaderSize int, metaBodySize int64, maxSize int64, endFunc func()) *FileWriter {
|
||||||
return &FileWriter{
|
return &FileWriter{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
key: key,
|
key: key,
|
||||||
rawWriter: rawWriter,
|
rawWriter: rawWriter,
|
||||||
expiredAt: expiredAt,
|
expiredAt: expiredAt,
|
||||||
maxSize: maxSize,
|
maxSize: maxSize,
|
||||||
endFunc: endFunc,
|
endFunc: endFunc,
|
||||||
|
metaHeaderSize: metaHeaderSize,
|
||||||
|
metaBodySize: metaBodySize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +52,10 @@ func (this *FileWriter) WriteHeader(data []byte) (n int, err error) {
|
|||||||
|
|
||||||
// WriteHeaderLength 写入Header长度数据
|
// WriteHeaderLength 写入Header长度数据
|
||||||
func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
||||||
bytes4 := make([]byte, 4)
|
if this.metaHeaderSize > 0 && this.metaHeaderSize == headerLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes4 = make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -88,7 +98,10 @@ func (this *FileWriter) WriteAt(offset int64, data []byte) error {
|
|||||||
|
|
||||||
// WriteBodyLength 写入Body长度数据
|
// WriteBodyLength 写入Body长度数据
|
||||||
func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
|
func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
|
||||||
bytes8 := make([]byte, 8)
|
if this.metaBodySize >= 0 && bodyLength == this.metaBodySize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes8 = make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,7 +122,7 @@ func (this *FileWriter) Close() error {
|
|||||||
this.endFunc()
|
this.endFunc()
|
||||||
})
|
})
|
||||||
|
|
||||||
path := this.rawWriter.Name()
|
var path = this.rawWriter.Name()
|
||||||
|
|
||||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PartialFileWriter struct {
|
type PartialFileWriter struct {
|
||||||
rawWriter *os.File
|
rawWriter *os.File
|
||||||
key string
|
key string
|
||||||
headerSize int64
|
|
||||||
bodySize int64
|
metaHeaderSize int
|
||||||
expiredAt int64
|
headerSize int64
|
||||||
endFunc func()
|
|
||||||
once sync.Once
|
metaBodySize int64
|
||||||
|
bodySize int64
|
||||||
|
|
||||||
|
expiredAt int64
|
||||||
|
endFunc func()
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
isNew bool
|
isNew bool
|
||||||
isPartial bool
|
isPartial bool
|
||||||
@@ -27,17 +32,19 @@ type PartialFileWriter struct {
|
|||||||
rangePath string
|
rangePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter {
|
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaHeaderSize int, metaBodySize int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter {
|
||||||
return &PartialFileWriter{
|
return &PartialFileWriter{
|
||||||
key: key,
|
key: key,
|
||||||
rawWriter: rawWriter,
|
rawWriter: rawWriter,
|
||||||
expiredAt: expiredAt,
|
expiredAt: expiredAt,
|
||||||
endFunc: endFunc,
|
endFunc: endFunc,
|
||||||
isNew: isNew,
|
isNew: isNew,
|
||||||
isPartial: isPartial,
|
isPartial: isPartial,
|
||||||
bodyOffset: bodyOffset,
|
bodyOffset: bodyOffset,
|
||||||
ranges: ranges,
|
ranges: ranges,
|
||||||
rangePath: partialRangesFilePath(rawWriter.Name()),
|
rangePath: partialRangesFilePath(rawWriter.Name()),
|
||||||
|
metaHeaderSize: metaHeaderSize,
|
||||||
|
metaBodySize: metaBodySize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +78,11 @@ func (this *PartialFileWriter) AppendHeader(data []byte) error {
|
|||||||
|
|
||||||
// WriteHeaderLength 写入Header长度数据
|
// WriteHeaderLength 写入Header长度数据
|
||||||
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
||||||
bytes4 := make([]byte, 4)
|
if this.metaHeaderSize > 0 && this.metaHeaderSize == headerLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes4 = make([]byte, 4)
|
||||||
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
binary.BigEndian.PutUint32(bytes4, uint32(headerLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,8 +121,13 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if this.bodyOffset == 0 {
|
if this.bodyOffset == 0 {
|
||||||
this.bodyOffset = SizeMeta + int64(len(this.key)) + this.headerSize
|
var keyLength = 0
|
||||||
|
if this.ranges.Version == 0 { // 以往的版本包含有Key
|
||||||
|
keyLength = len(this.key)
|
||||||
|
}
|
||||||
|
this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -129,7 +145,10 @@ func (this *PartialFileWriter) SetBodyLength(bodyLength int64) {
|
|||||||
|
|
||||||
// WriteBodyLength 写入Body长度数据
|
// WriteBodyLength 写入Body长度数据
|
||||||
func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
|
func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
|
||||||
bytes8 := make([]byte, 8)
|
if this.metaBodySize > 0 && this.metaBodySize == bodyLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes8 = make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
binary.BigEndian.PutUint64(bytes8, uint64(bodyLength))
|
||||||
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
_, err := this.rawWriter.Seek(SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -150,8 +169,11 @@ func (this *PartialFileWriter) Close() error {
|
|||||||
this.endFunc()
|
this.endFunc()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.ranges.BodySize = this.bodySize
|
||||||
err := this.ranges.WriteToFile(this.rangePath)
|
err := this.ranges.WriteToFile(this.rangePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = this.rawWriter.Close()
|
||||||
|
this.remove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func TestPartialFileWriter_Write(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var ranges = caches.NewPartialRanges()
|
var ranges = caches.NewPartialRanges(0)
|
||||||
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, true, true, 0, ranges, func() {
|
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, -1, -1, true, true, 0, ranges, func() {
|
||||||
t.Log("end")
|
t.Log("end")
|
||||||
})
|
})
|
||||||
_, err = writer.WriteHeader([]byte("header"))
|
_, err = writer.WriteHeader([]byte("header"))
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import (
|
|||||||
// APIConfig 节点API配置
|
// APIConfig 节点API配置
|
||||||
type APIConfig struct {
|
type APIConfig struct {
|
||||||
RPC struct {
|
RPC struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||||
DisableUpdate bool `yaml:"disableUpdate"`
|
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||||
} `yaml:"rpc"`
|
} `yaml:"rpc" json:"rpc"`
|
||||||
NodeId string `yaml:"nodeId"`
|
NodeId string `yaml:"nodeId" json:"nodeId"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret" json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIConfig() *APIConfig {
|
||||||
|
return &APIConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadAPIConfig() (*APIConfig, error) {
|
func LoadAPIConfig() (*APIConfig, error) {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package configs
|
|||||||
// ClusterConfig 集群配置
|
// ClusterConfig 集群配置
|
||||||
type ClusterConfig struct {
|
type ClusterConfig struct {
|
||||||
RPC struct {
|
RPC struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||||
DisableUpdate bool `yaml:"disableUpdate"`
|
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||||
} `yaml:"rpc"`
|
} `yaml:"rpc" json:"rpc"`
|
||||||
ClusterId string `yaml:"clusterId"`
|
ClusterId string `yaml:"clusterId" json:"clusterId"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret" json:"secret"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package configs
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
var sharedLocker = &sync.RWMutex{}
|
|
||||||
7
internal/conns/linger.go
Normal file
7
internal/conns/linger.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package conns
|
||||||
|
|
||||||
|
type LingerConn interface {
|
||||||
|
SetLinger(sec int) error
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
var SharedMap = NewMap()
|
var SharedMap = NewMap()
|
||||||
|
|
||||||
type Map struct {
|
type Map struct {
|
||||||
m map[string]map[int]net.Conn // ip => { port => Conn }
|
m map[string]map[int]net.Conn // ip => { port => Conn }
|
||||||
|
|
||||||
locker sync.RWMutex
|
locker sync.RWMutex
|
||||||
}
|
}
|
||||||
@@ -37,9 +37,7 @@ func (this *Map) Add(conn net.Conn) {
|
|||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
connMap, ok := this.m[ip]
|
connMap, ok := this.m[ip]
|
||||||
if !ok {
|
if !ok {
|
||||||
this.m[ip] = map[int]net.Conn{
|
this.m[ip] = map[int]net.Conn{port: conn}
|
||||||
port: conn,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
connMap[port] = conn
|
connMap[port] = conn
|
||||||
}
|
}
|
||||||
@@ -96,6 +94,13 @@ func (this *Map) CloseIPConns(ip string) {
|
|||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
for _, conn := range conns {
|
for _, conn := range conns {
|
||||||
|
// 设置Linger
|
||||||
|
lingerConn, isLingerConn := conn.(LingerConn)
|
||||||
|
if isLingerConn {
|
||||||
|
_ = lingerConn.SetLinger(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,9 +114,10 @@ func (this *Map) AllConns() []net.Conn {
|
|||||||
|
|
||||||
var result = []net.Conn{}
|
var result = []net.Conn{}
|
||||||
for _, m := range this.m {
|
for _, m := range this.m {
|
||||||
for _, conn := range m {
|
for _, connInfo := range m {
|
||||||
result = append(result, conn)
|
result = append(result, connInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.5.2"
|
Version = "0.6.2"
|
||||||
|
|
||||||
ProductName = "Edge Node"
|
ProductName = "Edge Node"
|
||||||
ProcessName = "edge-node"
|
ProcessName = "edge-node"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package firewalls
|
package firewalls
|
||||||
|
|
||||||
@@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
||||||
@@ -53,19 +54,13 @@ func init() {
|
|||||||
|
|
||||||
// DDoSProtectionManager DDoS防护
|
// DDoSProtectionManager DDoS防护
|
||||||
type DDoSProtectionManager struct {
|
type DDoSProtectionManager struct {
|
||||||
nftPath string
|
|
||||||
|
|
||||||
lastAllowIPList []string
|
lastAllowIPList []string
|
||||||
lastConfig []byte
|
lastConfig []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDDoSProtectionManager 获取新对象
|
// NewDDoSProtectionManager 获取新对象
|
||||||
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
||||||
nftPath, _ := exec.LookPath("nft")
|
return &DDoSProtectionManager{}
|
||||||
|
|
||||||
return &DDoSProtectionManager{
|
|
||||||
nftPath: nftPath,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply 应用配置
|
// Apply 应用配置
|
||||||
@@ -75,7 +70,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
|||||||
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||||
if nodeConfig != nil {
|
if nodeConfig != nil {
|
||||||
var allowIPList = nodeConfig.AllowedIPs
|
var allowIPList = nodeConfig.AllowedIPs
|
||||||
if !utils.ContainsSameStrings(allowIPList, this.lastAllowIPList) {
|
if !utils.EqualStrings(allowIPList, this.lastAllowIPList) {
|
||||||
allowIPListChanged = true
|
allowIPListChanged = true
|
||||||
this.lastAllowIPList = allowIPList
|
this.lastAllowIPList = allowIPList
|
||||||
}
|
}
|
||||||
@@ -91,11 +86,14 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
|||||||
}
|
}
|
||||||
remotelogs.Println("FIREWALL", "change DDoS protection config")
|
remotelogs.Println("FIREWALL", "change DDoS protection config")
|
||||||
|
|
||||||
if len(this.nftPath) == 0 {
|
if len(this.nftExe()) == 0 {
|
||||||
return errors.New("can not find nft command")
|
return errors.New("can not find nft command")
|
||||||
}
|
}
|
||||||
|
|
||||||
if nftablesInstance == nil {
|
if nftablesInstance == nil {
|
||||||
|
if config == nil || !config.IsOn() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return errors.New("nftables instance should not be nil")
|
return errors.New("nftables instance should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +152,11 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
|||||||
|
|
||||||
// 添加TCP规则
|
// 添加TCP规则
|
||||||
func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error {
|
func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error {
|
||||||
|
var nftExe = this.nftExe()
|
||||||
|
if len(nftExe) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 检查nft版本不能小于0.9
|
// 检查nft版本不能小于0.9
|
||||||
if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 {
|
if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -263,23 +266,21 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
|||||||
// 添加新规则
|
// 添加新规则
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
if maxConnections > 0 {
|
if maxConnections > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 让用户选择是drop还是reject
|
// TODO 让用户选择是drop还是reject
|
||||||
if maxConnectionsPerIP > 0 {
|
if maxConnectionsPerIP > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,20 +288,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
|||||||
// TODO 让用户选择是drop还是reject
|
// TODO 让用户选择是drop还是reject
|
||||||
if newConnectionsMinutelyRate > 0 {
|
if newConnectionsMinutelyRate > 0 {
|
||||||
if newConnectionsMinutelyRateBlockTimeout > 0 {
|
if newConnectionsMinutelyRateBlockTimeout > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,20 +308,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
|||||||
// TODO 让用户选择是drop还是reject
|
// TODO 让用户选择是drop还是reject
|
||||||
if newConnectionsSecondlyRate > 0 {
|
if newConnectionsSecondlyRate > 0 {
|
||||||
if newConnectionsSecondlyRateBlockTimeout > 0 {
|
if newConnectionsSecondlyRateBlockTimeout > 0 {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var cmd = exec.Command(this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
|
||||||
var stderr = &bytes.Buffer{}
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")")
|
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,3 +552,8 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *DDoSProtectionManager) nftExe() string {
|
||||||
|
path, _ := exec.LookPath("nft")
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
var SharedDDoSProtectionManager = NewDDoSProtectionManager()
|
||||||
|
|
||||||
type DDoSProtectionManager struct {
|
type DDoSProtectionManager struct {
|
||||||
nftPath string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
func NewDDoSProtectionManager() *DDoSProtectionManager {
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type firewalldCmd struct {
|
type firewalldCmd struct {
|
||||||
cmd *exec.Cmd
|
cmd *executils.Cmd
|
||||||
denyIP string
|
denyIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ func NewFirewalld() *Firewalld {
|
|||||||
|
|
||||||
path, err := exec.LookPath("firewall-cmd")
|
path, err := exec.LookPath("firewall-cmd")
|
||||||
if err == nil && len(path) > 0 {
|
if err == nil && len(path) > 0 {
|
||||||
var cmd = exec.Command(path, "--state")
|
var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
firewalld.exe = path
|
firewalld.exe = path
|
||||||
@@ -85,7 +87,7 @@ func (this *Firewalld) AllowPort(port int, protocol string) error {
|
|||||||
if !this.isReady {
|
if !this.isReady {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, "--add-port="+types.String(port)+"/"+protocol)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+types.String(port)+"/"+protocol)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -95,12 +97,12 @@ func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol
|
|||||||
var port = this.PortRangeString(portRange, protocol)
|
var port = this.PortRangeString(portRange, protocol)
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--add-port="+port, "--permanent")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port, "--permanent")
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--add-port="+port)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,7 @@ func (this *Firewalld) RemovePort(port int, protocol string) error {
|
|||||||
if !this.isReady {
|
if !this.isReady {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, "--remove-port="+types.String(port)+"/"+protocol)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+types.String(port)+"/"+protocol)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,12 +123,12 @@ func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol str
|
|||||||
var port = this.PortRangeString(portRange, protocol)
|
var port = this.PortRangeString(portRange, protocol)
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--remove-port="+port, "--permanent")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port, "--permanent")
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var cmd = exec.Command(this.exe, "--remove-port="+port)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +161,7 @@ func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
|
|||||||
if timeoutSeconds > 0 {
|
if timeoutSeconds > 0 {
|
||||||
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, args...)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
|
||||||
this.pushCmd(cmd, ip)
|
this.pushCmd(cmd, ip)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -182,7 +184,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
|
|||||||
if timeoutSeconds > 0 {
|
if timeoutSeconds > 0 {
|
||||||
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(this.exe, args...)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
|
||||||
if async {
|
if async {
|
||||||
this.pushCmd(cmd, ip)
|
this.pushCmd(cmd, ip)
|
||||||
return nil
|
return nil
|
||||||
@@ -209,13 +211,13 @@ func (this *Firewalld) RemoveSourceIP(ip string) error {
|
|||||||
}
|
}
|
||||||
for _, action := range []string{"reject", "drop"} {
|
for _, action := range []string{"reject", "drop"} {
|
||||||
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
|
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
|
||||||
var cmd = exec.Command(this.exe, args...)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
|
||||||
this.pushCmd(cmd, "")
|
this.pushCmd(cmd, "")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Firewalld) pushCmd(cmd *exec.Cmd, denyIP string) {
|
func (this *Firewalld) pushCmd(cmd *executils.Cmd, denyIP string) {
|
||||||
select {
|
select {
|
||||||
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
|
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
package firewalls
|
package firewalls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
@@ -13,6 +12,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -432,15 +432,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
|||||||
|
|
||||||
// 读取版本号
|
// 读取版本号
|
||||||
func (this *NFTablesFirewall) readVersion(nftPath string) string {
|
func (this *NFTablesFirewall) readVersion(nftPath string) string {
|
||||||
var cmd = exec.Command(nftPath, "--version")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nftPath, "--version")
|
||||||
var output = &bytes.Buffer{}
|
cmd.WithStdout()
|
||||||
cmd.Stdout = output
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputString = output.String()
|
var outputString = cmd.Stdout()
|
||||||
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
|
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
|
||||||
if len(versionMatches) <= 1 {
|
if len(versionMatches) <= 1 {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
108
internal/firewalls/nftables/installer.go
Normal file
108
internal/firewalls/nftables/installer.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package nftables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
events.On(events.EventReload, func() {
|
||||||
|
// linux only
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeConfig == nil || !nodeConfig.AutoInstallNftables {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getgid() == 0 { // root user only
|
||||||
|
_, err := exec.LookPath("nft")
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goman.New(func() {
|
||||||
|
err := NewInstaller().Install()
|
||||||
|
if err != nil {
|
||||||
|
// 不需要传到API节点
|
||||||
|
logs.Println("[NFTABLES]install nftables failed: " + err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Installer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInstaller() *Installer {
|
||||||
|
return &Installer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Installer) Install() error {
|
||||||
|
// linux only
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已经存在
|
||||||
|
_, err := exec.LookPath("nft")
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd *executils.Cmd
|
||||||
|
|
||||||
|
// check dnf
|
||||||
|
dnfExe, err := exec.LookPath("dnf")
|
||||||
|
if err == nil {
|
||||||
|
cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check apt
|
||||||
|
if cmd == nil {
|
||||||
|
aptExe, err := exec.LookPath("apt")
|
||||||
|
if err == nil {
|
||||||
|
cmd = executils.NewCmd(aptExe, "install", "nftables")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check yum
|
||||||
|
if cmd == nil {
|
||||||
|
yumExe, err := exec.LookPath("yum")
|
||||||
|
if err == nil {
|
||||||
|
cmd = executils.NewCmd(yumExe, "-y", "install", "nftables")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.WithTimeout(10 * time.Minute)
|
||||||
|
cmd.WithStderr()
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(err.Error() + ": " + cmd.Stderr())
|
||||||
|
}
|
||||||
|
|
||||||
|
remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables
|
package nftables
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
package nftables_test
|
package nftables_test
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
|
|
||||||
// FirewalldAction Firewalld动作管理
|
// FirewalldAction Firewalld动作管理
|
||||||
// 常用命令:
|
// 常用命令:
|
||||||
// - 查询列表: firewall-cmd --list-all
|
// - 查询列表: firewall-cmd --list-all
|
||||||
// - 添加IP:firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
// - 添加IP:firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||||
// - 删除IP:firewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
// - 删除IP:firewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||||
type FirewalldAction struct {
|
type FirewalldAction struct {
|
||||||
BaseAction
|
BaseAction
|
||||||
|
|
||||||
@@ -144,12 +144,11 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
|||||||
// MAC OS直接返回
|
// MAC OS直接返回
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmd := exec.Command(path, args...)
|
cmd := executils.NewTimeoutCmd(30*time.Second, path, args...)
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(err.Error() + ", output: " + string(stderr.Bytes()))
|
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -16,12 +16,12 @@ import (
|
|||||||
// IPSetAction IPSet动作
|
// IPSetAction IPSet动作
|
||||||
// 相关命令:
|
// 相关命令:
|
||||||
// - 利用Firewalld管理set:
|
// - 利用Firewalld管理set:
|
||||||
// - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
|
// - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
|
||||||
// - 删除:firewall-cmd --permanent --delete-ipset=edge_ip_list
|
// - 删除:firewall-cmd --permanent --delete-ipset=edge_ip_list
|
||||||
// - 重载:firewall-cmd --reload
|
// - 重载:firewall-cmd --reload
|
||||||
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
|
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
|
||||||
// - 利用IPTables管理set:
|
// - 利用IPTables管理set:
|
||||||
// - 添加:iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
|
// - 添加:iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
|
||||||
// - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30
|
// - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30
|
||||||
// - 删除Item: ipset del edge_ip_list 192.168.2.32
|
// - 删除Item: ipset del edge_ip_list 192.168.2.32
|
||||||
// - 创建set:ipset create edge_ip_list hash:ip timeout 0
|
// - 创建set:ipset create edge_ip_list hash:ip timeout 0
|
||||||
@@ -30,16 +30,13 @@ import (
|
|||||||
type IPSetAction struct {
|
type IPSetAction struct {
|
||||||
BaseAction
|
BaseAction
|
||||||
|
|
||||||
config *firewallconfigs.FirewallActionIPSetConfig
|
config *firewallconfigs.FirewallActionIPSetConfig
|
||||||
errorBuf *bytes.Buffer
|
|
||||||
|
|
||||||
ipsetNotfound bool
|
ipsetNotfound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIPSetAction() *IPSetAction {
|
func NewIPSetAction() *IPSetAction {
|
||||||
return &IPSetAction{
|
return &IPSetAction{}
|
||||||
errorBuf: &bytes.Buffer{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
@@ -68,14 +65,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if !bytes.Contains(output, []byte("already exists")) {
|
if !strings.Contains(output, "already exists") {
|
||||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -87,14 +83,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if !bytes.Contains(output, []byte("already exists")) {
|
if !strings.Contains(output, "already exists") {
|
||||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -114,16 +109,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
if strings.Contains(output, "NAME_CONFLICT") {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,16 +127,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
if strings.Contains(output, "NAME_CONFLICT") {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,13 +145,11 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,25 +158,21 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload
|
// reload
|
||||||
{
|
{
|
||||||
cmd := exec.Command(path, "--reload")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--reload")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,19 +191,17 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查规则是否存在
|
// 检查规则是否存在
|
||||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
var exists = err == nil
|
var exists = err == nil
|
||||||
|
|
||||||
// 添加规则
|
// 添加规则
|
||||||
if !exists {
|
if !exists {
|
||||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,18 +213,16 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查规则是否存在
|
// 检查规则是否存在
|
||||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
var exists = err == nil
|
var exists = err == nil
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
||||||
var stderr = bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var output = stderr.Bytes()
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,12 +344,11 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errorBuf.Reset()
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
|
||||||
var cmd = exec.Command(path, args...)
|
cmd.WithStderr()
|
||||||
cmd.Stderr = this.errorBuf
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errString = this.errorBuf.String()
|
var errString = cmd.Stderr()
|
||||||
if action == "deleteItem" && strings.Contains(errString, "not added") {
|
if action == "deleteItem" && strings.Contains(errString, "not added") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPTablesAction IPTables动作
|
// IPTablesAction IPTables动作
|
||||||
// 相关命令:
|
// 相关命令:
|
||||||
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT
|
//
|
||||||
// iptables -A INPUT -s "192.168.2.32" -j REJECT
|
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT
|
||||||
// iptables -D INPUT ...
|
// iptables -A INPUT -s "192.168.2.32" -j REJECT
|
||||||
// iptables -F INPUT
|
// iptables -D INPUT ...
|
||||||
|
// iptables -F INPUT
|
||||||
type IPTablesAction struct {
|
type IPTablesAction struct {
|
||||||
BaseAction
|
BaseAction
|
||||||
|
|
||||||
@@ -71,10 +75,16 @@ func (this *IPTablesAction) runAction(action string, listType IPListType, item *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
// 暂时不支持ipv6
|
||||||
|
// TODO 将来支持ipv6
|
||||||
|
if utils.IsIPv6(item.IpFrom) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if item.Type == "all" {
|
if item.Type == "all" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
path := this.config.Path
|
var path = this.config.Path
|
||||||
var err error
|
var err error
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
path, err = exec.LookPath("iptables")
|
path, err = exec.LookPath("iptables")
|
||||||
@@ -85,6 +95,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
|||||||
this.iptablesNotFound = true
|
this.iptablesNotFound = true
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
this.config.Path = path
|
||||||
}
|
}
|
||||||
iptablesAction := ""
|
iptablesAction := ""
|
||||||
switch action {
|
switch action {
|
||||||
@@ -110,16 +121,15 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(path, args...)
|
var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = cmd.Stderr()
|
||||||
if bytes.Contains(output, []byte("No chain/target/match")) {
|
if strings.Contains(output, "No chain/target/match") {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New(err.Error() + ", output: " + string(output))
|
return errors.New(err.Error() + ", output: " + output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
|
|||||||
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type+": "+err.Error())
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type+": "+err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Compare(newConfigJSON, oldConfigJSON) != 0 {
|
if !bytes.Equal(newConfigJSON, oldConfigJSON) {
|
||||||
_ = oldInstance.Close()
|
_ = oldInstance.Close()
|
||||||
|
|
||||||
// 重新创建
|
// 重新创建
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"os/exec"
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScriptAction 脚本命令动作
|
// ScriptAction 脚本命令动作
|
||||||
@@ -45,25 +45,24 @@ func (this *ScriptAction) DeleteItem(listType IPListType, item *pb.IPItem) error
|
|||||||
|
|
||||||
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
// TODO 智能支持 .sh 脚本文件
|
// TODO 智能支持 .sh 脚本文件
|
||||||
cmd := exec.Command(this.config.Path)
|
var cmd = executils.NewTimeoutCmd(30*time.Second, this.config.Path)
|
||||||
cmd.Env = []string{
|
cmd.WithEnv([]string{
|
||||||
"ACTION=" + action,
|
"ACTION=" + action,
|
||||||
"TYPE=" + item.Type,
|
"TYPE=" + item.Type,
|
||||||
"IP_FROM=" + item.IpFrom,
|
"IP_FROM=" + item.IpFrom,
|
||||||
"IP_TO=" + item.IpTo,
|
"IP_TO=" + item.IpTo,
|
||||||
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
|
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
|
||||||
"LIST_TYPE=" + listType,
|
"LIST_TYPE=" + listType,
|
||||||
}
|
})
|
||||||
if len(this.config.Cwd) > 0 {
|
if len(this.config.Cwd) > 0 {
|
||||||
cmd.Dir = this.config.Cwd
|
cmd.WithDir(this.config.Cwd)
|
||||||
} else {
|
} else {
|
||||||
cmd.Dir = filepath.Dir(this.config.Path)
|
cmd.WithDir(filepath.Dir(this.config.Path))
|
||||||
}
|
}
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
cmd.WithStderr()
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(err.Error() + ", output: " + string(stderr.Bytes()))
|
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ func (this *IPListDB) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var path = this.dir + "/ip_list.db"
|
var path = this.dir + "/ip_list.db"
|
||||||
_ = os.Remove(path + "-shm")
|
|
||||||
_ = os.Remove(path + "-wal")
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,6 +69,14 @@ func (this *IPListDB) init() error {
|
|||||||
|
|
||||||
this.db = db
|
this.db = db
|
||||||
|
|
||||||
|
// 恢复数据库
|
||||||
|
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||||
|
if len(recoverEnv) > 0 {
|
||||||
|
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
|
||||||
|
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化数据库
|
// 初始化数据库
|
||||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -164,6 +170,12 @@ func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是删除,则不再创建新记录
|
||||||
|
if item.IsDeleted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
@@ -18,6 +20,10 @@ var SharedIPListManager = NewIPListManager()
|
|||||||
var IPListUpdateNotify = make(chan bool, 1)
|
var IPListUpdateNotify = make(chan bool, 1)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
if teaconst.IsDaemon {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
events.On(events.EventLoaded, func() {
|
events.On(events.EventLoaded, func() {
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
SharedIPListManager.Start()
|
SharedIPListManager.Start()
|
||||||
@@ -26,6 +32,13 @@ func init() {
|
|||||||
events.On(events.EventQuit, func() {
|
events.On(events.EventQuit, func() {
|
||||||
SharedIPListManager.Stop()
|
SharedIPListManager.Stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var ticker = time.NewTicker(24 * time.Hour)
|
||||||
|
goman.New(func() {
|
||||||
|
for range ticker.C {
|
||||||
|
SharedIPListManager.DeleteExpiredItems()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPListManager IP名单管理
|
// IPListManager IP名单管理
|
||||||
@@ -129,6 +142,12 @@ func (this *IPListManager) init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPListManager) loop() error {
|
func (this *IPListManager) loop() error {
|
||||||
|
// 是否同步IP名单
|
||||||
|
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||||
|
if nodeConfig != nil && !nodeConfig.EnableIPLists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
hasNext, err := this.fetch()
|
hasNext, err := this.fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -186,6 +205,12 @@ func (this *IPListManager) FindList(listId int64) *IPList {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *IPListManager) DeleteExpiredItems() {
|
||||||
|
if this.db != nil {
|
||||||
|
_ = this.db.DeleteExpiredItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||||
var changedLists = map[*IPList]zero.Zero{}
|
var changedLists = map[*IPList]zero.Zero{}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ func (this *Task) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
|
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
|
||||||
_ = os.Remove(path + "-shm")
|
|
||||||
_ = os.Remove(path + "-wal")
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
db, err := sql.Open("sqlite3", "file:"+path+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,6 +99,14 @@ func (this *Task) Init() error {
|
|||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
this.db = dbs.NewDB(db)
|
this.db = dbs.NewDB(db)
|
||||||
|
|
||||||
|
// 恢复数据库
|
||||||
|
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||||
|
if len(recoverEnv) > 0 {
|
||||||
|
for _, indexName := range []string{"serverId", "hash"} {
|
||||||
|
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if teaconst.EnableDBStat {
|
if teaconst.EnableDBStat {
|
||||||
this.db.EnableStat(true)
|
this.db.EnableStat(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -17,7 +16,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -179,7 +178,7 @@ func (this *APIStream) handleWriteCache(message *pb.NodeStreamMessage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expiredAt := time.Now().Unix() + msg.LifeSeconds
|
expiredAt := time.Now().Unix() + msg.LifeSeconds
|
||||||
writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, int64(len(msg.Value)), -1, false)
|
writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, -1, int64(len(msg.Value)), -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
|
this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -347,15 +346,15 @@ func (this *APIStream) handleCheckSystemdService(message *pb.NodeStreamMessage)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd = utils.NewCommandExecutor()
|
var shortName = teaconst.SystemdServiceName
|
||||||
shortName := teaconst.SystemdServiceName
|
var cmd = executils.NewTimeoutCmd(10*time.Second, systemctl, "is-enabled", shortName)
|
||||||
cmd.Add(systemctl, "is-enabled", shortName)
|
cmd.WithStdout()
|
||||||
output, err := cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error())
|
this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if output == "enabled" {
|
if cmd.Stdout() == "enabled" {
|
||||||
this.replyOk(message.RequestId, "ok")
|
this.replyOk(message.RequestId, "ok")
|
||||||
} else {
|
} else {
|
||||||
this.replyFail(message.RequestId, "not installed")
|
this.replyFail(message.RequestId, "not installed")
|
||||||
@@ -385,16 +384,15 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd = exec.Command(nft, "--version")
|
var cmd = executils.NewTimeoutCmd(10*time.Second, nft, "--version")
|
||||||
var output = &bytes.Buffer{}
|
cmd.WithStdout()
|
||||||
cmd.Stdout = output
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.replyFail(message.RequestId, "get version failed: "+err.Error())
|
this.replyFail(message.RequestId, "get version failed: "+err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputString = output.String()
|
var outputString = cmd.Stdout()
|
||||||
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
|
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
|
||||||
if len(versionMatches) <= 1 {
|
if len(versionMatches) <= 1 {
|
||||||
this.replyFail(message.RequestId, "can not get nft version")
|
this.replyFail(message.RequestId, "can not get nft version")
|
||||||
@@ -409,7 +407,7 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
|
|||||||
var protectionConfig = sharedNodeConfig.DDoSProtection
|
var protectionConfig = sharedNodeConfig.DDoSProtection
|
||||||
err = firewalls.SharedDDoSProtectionManager.Apply(protectionConfig)
|
err = firewalls.SharedDDoSProtectionManager.Apply(protectionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.replyFail(message.RequestId, dataMessage.Name+"was installed, but apply DDoS protection config failed: "+err.Error())
|
this.replyFail(message.RequestId, dataMessage.Name+" was installed, but apply DDoS protection config failed: "+err.Error())
|
||||||
} else {
|
} else {
|
||||||
this.replyOk(message.RequestId, string(result.AsJSON()))
|
this.replyOk(message.RequestId, string(result.AsJSON()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||||
@@ -17,7 +18,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -26,18 +26,30 @@ import (
|
|||||||
type ClientConn struct {
|
type ClientConn struct {
|
||||||
BaseClientConn
|
BaseClientConn
|
||||||
|
|
||||||
once sync.Once
|
createdAt int64
|
||||||
|
|
||||||
isTLS bool
|
isTLS bool
|
||||||
hasDeadline bool
|
isHTTP bool
|
||||||
hasRead bool
|
hasRead bool
|
||||||
|
|
||||||
isLO bool // 是否为环路
|
isLO bool // 是否为环路
|
||||||
|
isInAllowList bool
|
||||||
|
|
||||||
hasResetSYNFlood bool
|
hasResetSYNFlood bool
|
||||||
|
|
||||||
|
lastReadAt int64
|
||||||
|
lastWriteAt int64
|
||||||
|
lastErr error
|
||||||
|
|
||||||
|
readDeadlineTime int64
|
||||||
|
isShortReading bool // reading header or tls handshake
|
||||||
|
|
||||||
|
isDebugging bool
|
||||||
|
autoReadTimeout bool
|
||||||
|
autoWriteTimeout bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool) net.Conn {
|
func NewClientConn(rawConn net.Conn, isHTTP bool, isTLS bool, isInAllowList bool) net.Conn {
|
||||||
// 是否为环路
|
// 是否为环路
|
||||||
var remoteAddr = rawConn.RemoteAddr().String()
|
var remoteAddr = rawConn.RemoteAddr().String()
|
||||||
var isLO = strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:")
|
var isLO = strings.HasPrefix(remoteAddr, "127.0.0.1:") || strings.HasPrefix(remoteAddr, "[::1]:")
|
||||||
@@ -45,10 +57,21 @@ func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool) net.Conn {
|
|||||||
var conn = &ClientConn{
|
var conn = &ClientConn{
|
||||||
BaseClientConn: BaseClientConn{rawConn: rawConn},
|
BaseClientConn: BaseClientConn{rawConn: rawConn},
|
||||||
isTLS: isTLS,
|
isTLS: isTLS,
|
||||||
|
isHTTP: isHTTP,
|
||||||
isLO: isLO,
|
isLO: isLO,
|
||||||
|
isInAllowList: isInAllowList,
|
||||||
|
createdAt: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if quickClose {
|
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
|
||||||
|
if globalServerConfig != nil {
|
||||||
|
var performanceConfig = globalServerConfig.Performance
|
||||||
|
conn.isDebugging = performanceConfig.Debug
|
||||||
|
conn.autoReadTimeout = performanceConfig.AutoReadTimeout
|
||||||
|
conn.autoWriteTimeout = performanceConfig.AutoWriteTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
if isHTTP {
|
||||||
// TODO 可以在配置中设置此值
|
// TODO 可以在配置中设置此值
|
||||||
_ = conn.SetLinger(nodeconfigs.DefaultTCPLinger)
|
_ = conn.SetLinger(nodeconfigs.DefaultTCPLinger)
|
||||||
}
|
}
|
||||||
@@ -60,6 +83,16 @@ func NewClientConn(rawConn net.Conn, isTLS bool, quickClose bool) net.Conn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||||
|
if this.isDebugging {
|
||||||
|
this.lastReadAt = time.Now().Unix()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
this.lastErr = errors.New("read error: " + err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// 环路直接读取
|
// 环路直接读取
|
||||||
if this.isLO {
|
if this.isLO {
|
||||||
n, err = this.rawConn.Read(b)
|
n, err = this.rawConn.Read(b)
|
||||||
@@ -69,40 +102,39 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS
|
// 设置读超时时间
|
||||||
if this.isTLS {
|
if this.isHTTP && !this.isWebsocket && !this.isShortReading && this.autoReadTimeout {
|
||||||
if !this.hasDeadline {
|
this.setHTTPReadTimeout()
|
||||||
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
|
|
||||||
this.hasDeadline = true
|
|
||||||
defer func() {
|
|
||||||
_ = this.rawConn.SetReadDeadline(time.Time{})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始读取
|
// 开始读取
|
||||||
n, err = this.rawConn.Read(b)
|
n, err = this.rawConn.Read(b)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
||||||
if !this.hasRead {
|
this.hasRead = true
|
||||||
this.hasRead = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isHandshakeError = err != nil && os.IsTimeout(err) && !this.hasRead
|
// 检测是否为超时错误
|
||||||
if isHandshakeError {
|
var isTimeout = err != nil && os.IsTimeout(err)
|
||||||
|
var isHandshakeError = isTimeout && !this.hasRead
|
||||||
|
if isTimeout {
|
||||||
_ = this.SetLinger(0)
|
_ = this.SetLinger(0)
|
||||||
|
} else {
|
||||||
|
_ = this.SetLinger(nodeconfigs.DefaultTCPLinger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SYN Flood检测
|
// 忽略白名单和局域网
|
||||||
if this.serverId == 0 || !this.hasResetSYNFlood {
|
if this.isHTTP && !this.isInAllowList && !utils.IsLocalIP(this.RawIP()) {
|
||||||
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
|
// SYN Flood检测
|
||||||
if synFloodConfig != nil && synFloodConfig.IsOn {
|
if this.serverId == 0 || !this.hasResetSYNFlood {
|
||||||
if isHandshakeError {
|
var synFloodConfig = sharedNodeConfig.SYNFloodConfig()
|
||||||
this.increaseSYNFlood(synFloodConfig)
|
if synFloodConfig != nil && synFloodConfig.IsOn {
|
||||||
} else if err == nil && !this.hasResetSYNFlood {
|
if isHandshakeError {
|
||||||
this.hasResetSYNFlood = true
|
this.increaseSYNFlood(synFloodConfig)
|
||||||
this.resetSYNFlood()
|
} else if err == nil && !this.hasResetSYNFlood {
|
||||||
|
this.hasResetSYNFlood = true
|
||||||
|
this.resetSYNFlood()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +143,32 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *ClientConn) Write(b []byte) (n int, err error) {
|
func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||||
|
if this.isDebugging {
|
||||||
|
this.lastWriteAt = time.Now().Unix()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
this.lastErr = errors.New("write error: " + err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置写超时时间
|
||||||
|
if this.autoWriteTimeout {
|
||||||
|
// TODO L2 -> L1 写入时不限制时间
|
||||||
|
var timeoutSeconds = len(b) / 1024
|
||||||
|
if timeoutSeconds < 3 {
|
||||||
|
timeoutSeconds = 3
|
||||||
|
}
|
||||||
|
_ = this.rawConn.SetWriteDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second)) // TODO 时间可以设置
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延长读超时时间
|
||||||
|
if this.isHTTP && !this.isWebsocket && this.autoReadTimeout {
|
||||||
|
this.setHTTPReadTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始写入
|
||||||
n, err = this.rawConn.Write(b)
|
n, err = this.rawConn.Write(b)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
// 统计当前服务带宽
|
// 统计当前服务带宽
|
||||||
@@ -122,6 +180,17 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是写入超时,则立即关闭连接
|
||||||
|
if err != nil && os.IsTimeout(err) {
|
||||||
|
// TODO 考虑对多次慢连接的IP做出惩罚
|
||||||
|
conn, ok := this.rawConn.(LingerConn)
|
||||||
|
if ok {
|
||||||
|
_ = conn.SetLinger(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = this.Close()
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +222,26 @@ func (this *ClientConn) SetDeadline(t time.Time) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *ClientConn) SetReadDeadline(t time.Time) error {
|
func (this *ClientConn) SetReadDeadline(t time.Time) error {
|
||||||
|
// 如果开启了HTTP自动读超时选项,则自动控制超时时间
|
||||||
|
if this.isHTTP && !this.isWebsocket && this.autoReadTimeout {
|
||||||
|
this.isShortReading = false
|
||||||
|
|
||||||
|
var unixTime = t.Unix()
|
||||||
|
if unixTime < 10 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if unixTime == this.readDeadlineTime {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
this.readDeadlineTime = unixTime
|
||||||
|
var seconds = -time.Since(t)
|
||||||
|
if seconds <= 0 || seconds > HTTPIdleTimeout {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if seconds < HTTPIdleTimeout-1*time.Second {
|
||||||
|
this.isShortReading = true
|
||||||
|
}
|
||||||
|
}
|
||||||
return this.rawConn.SetReadDeadline(t)
|
return this.rawConn.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +249,22 @@ func (this *ClientConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return this.rawConn.SetWriteDeadline(t)
|
return this.rawConn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *ClientConn) CreatedAt() int64 {
|
||||||
|
return this.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ClientConn) LastReadAt() int64 {
|
||||||
|
return this.lastReadAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ClientConn) LastWriteAt() int64 {
|
||||||
|
return this.lastWriteAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ClientConn) LastErr() error {
|
||||||
|
return this.lastErr
|
||||||
|
}
|
||||||
|
|
||||||
func (this *ClientConn) resetSYNFlood() {
|
func (this *ClientConn) resetSYNFlood() {
|
||||||
ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
|
ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
|
||||||
}
|
}
|
||||||
@@ -191,3 +296,8 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置读超时时间
|
||||||
|
func (this *ClientConn) setHTTPReadTimeout() {
|
||||||
|
_ = this.SetReadDeadline(time.Now().Add(HTTPIdleTimeout))
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ type BaseClientConn struct {
|
|||||||
remoteAddr string
|
remoteAddr string
|
||||||
hasLimit bool
|
hasLimit bool
|
||||||
|
|
||||||
|
isWebsocket bool
|
||||||
|
|
||||||
isClosed bool
|
isClosed bool
|
||||||
|
|
||||||
|
rawIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *BaseClientConn) IsClosed() bool {
|
func (this *BaseClientConn) IsClosed() bool {
|
||||||
@@ -86,7 +90,12 @@ func (this *BaseClientConn) UserId() int64 {
|
|||||||
|
|
||||||
// RawIP 原本IP
|
// RawIP 原本IP
|
||||||
func (this *BaseClientConn) RawIP() string {
|
func (this *BaseClientConn) RawIP() string {
|
||||||
|
if len(this.rawIP) > 0 {
|
||||||
|
return this.rawIP
|
||||||
|
}
|
||||||
|
|
||||||
ip, _, _ := net.SplitHostPort(this.rawConn.RemoteAddr().String())
|
ip, _, _ := net.SplitHostPort(this.rawConn.RemoteAddr().String())
|
||||||
|
this.rawIP = ip
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,3 +124,7 @@ func (this *BaseClientConn) SetLinger(seconds int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *BaseClientConn) SetIsWebsocket(isWebsocket bool) {
|
||||||
|
this.isWebsocket = isWebsocket
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ type ClientConnInterface interface {
|
|||||||
|
|
||||||
// UserId 获取当前连接所属服务的用户ID
|
// UserId 获取当前连接所属服务的用户ID
|
||||||
UserId() int64
|
UserId() int64
|
||||||
|
|
||||||
|
// SetIsWebsocket 设置是否为Websocket
|
||||||
|
SetIsWebsocket(isWebsocket bool)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
// ClientListener 客户端网络监听
|
// ClientListener 客户端网络监听
|
||||||
type ClientListener struct {
|
type ClientListener struct {
|
||||||
rawListener net.Listener
|
rawListener net.Listener
|
||||||
|
isHTTP bool
|
||||||
isTLS bool
|
isTLS bool
|
||||||
quickClose bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientListener(listener net.Listener, quickClose bool) *ClientListener {
|
func NewClientListener(listener net.Listener, isHTTP bool) *ClientListener {
|
||||||
return &ClientListener{
|
return &ClientListener{
|
||||||
rawListener: listener,
|
rawListener: listener,
|
||||||
quickClose: quickClose,
|
isHTTP: isHTTP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +41,10 @@ func (this *ClientListener) Accept() (net.Conn, error) {
|
|||||||
|
|
||||||
// 是否在WAF名单中
|
// 是否在WAF名单中
|
||||||
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
var isInAllowList = false
|
||||||
if err == nil {
|
if err == nil {
|
||||||
canGoNext, _ := iplibrary.AllowIP(ip, 0)
|
canGoNext, inAllowList := iplibrary.AllowIP(ip, 0)
|
||||||
|
isInAllowList = inAllowList
|
||||||
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) {
|
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) {
|
||||||
expiresAt, ok := waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
|
expiresAt, ok := waf.SharedIPBlackList.ContainsExpires(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -76,7 +78,7 @@ func (this *ClientListener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewClientConn(conn, this.isTLS, this.quickClose), nil
|
return NewClientConn(conn, this.isHTTP, this.isTLS, isInAllowList), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *ClientListener) Close() error {
|
func (this *ClientListener) Close() error {
|
||||||
|
|||||||
@@ -55,3 +55,16 @@ func (this *ClientTLSConn) SetReadDeadline(t time.Time) error {
|
|||||||
func (this *ClientTLSConn) SetWriteDeadline(t time.Time) error {
|
func (this *ClientTLSConn) SetWriteDeadline(t time.Time) error {
|
||||||
return this.rawConn.SetWriteDeadline(t)
|
return this.rawConn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *ClientTLSConn) SetIsWebsocket(isWebsocket bool) {
|
||||||
|
tlsConn, ok := this.rawConn.(*tls.Conn)
|
||||||
|
if ok {
|
||||||
|
var rawConn = tlsConn.NetConn()
|
||||||
|
if rawConn != nil {
|
||||||
|
clientConn, ok := rawConn.(*ClientConn)
|
||||||
|
if ok {
|
||||||
|
clientConn.SetIsWebsocket(isWebsocket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -41,7 +43,11 @@ func (this *HTTPAccessLogQueue) Start() {
|
|||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
err := this.loop()
|
err := this.loop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("ACCESS_LOG_QUEUE", err.Error())
|
if rpc.IsConnError(err) {
|
||||||
|
remotelogs.Debug("ACCESS_LOG_QUEUE", err.Error())
|
||||||
|
} else {
|
||||||
|
remotelogs.Error("ACCESS_LOG_QUEUE", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,6 +115,19 @@ Loop:
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否请求内容过大
|
||||||
|
statusCode, ok := status.FromError(err)
|
||||||
|
if ok && statusCode.Code() == codes.ResourceExhausted {
|
||||||
|
// 去除Body
|
||||||
|
for _, accessLog := range accessLogs {
|
||||||
|
accessLog.RequestBody = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新提交
|
||||||
|
_, err = this.rpcClient.HTTPAccessLogRPC.CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,11 +95,11 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
|||||||
numberCPU = 8
|
numberCPU = 8
|
||||||
}
|
}
|
||||||
if maxConnections <= 0 {
|
if maxConnections <= 0 {
|
||||||
maxConnections = numberCPU * 32
|
maxConnections = numberCPU * 64
|
||||||
}
|
}
|
||||||
|
|
||||||
if idleConns <= 0 {
|
if idleConns <= 0 {
|
||||||
idleConns = numberCPU * 8
|
idleConns = numberCPU * 16
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可以判断为Ln节点请求
|
// 可以判断为Ln节点请求
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ type HTTPRequest struct {
|
|||||||
filePath string // 请求的文件名,仅在读取Root目录下的内容时不为空
|
filePath string // 请求的文件名,仅在读取Root目录下的内容时不为空
|
||||||
origin *serverconfigs.OriginConfig // 源站
|
origin *serverconfigs.OriginConfig // 源站
|
||||||
originAddr string // 源站实际地址
|
originAddr string // 源站实际地址
|
||||||
|
originStatus int32 // 源站响应代码
|
||||||
errors []string // 错误信息
|
errors []string // 错误信息
|
||||||
rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则
|
rewriteRule *serverconfigs.HTTPRewriteRule // 匹配到的重写规则
|
||||||
rewriteReplace string // 重写规则的目标
|
rewriteReplace string // 重写规则的目标
|
||||||
@@ -228,6 +229,22 @@ func (this *HTTPRequest) Do() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防盗链
|
||||||
|
if !this.isSubRequest && this.web.Referers != nil && this.web.Referers.IsOn {
|
||||||
|
if this.doCheckReferers() {
|
||||||
|
this.doEnd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UA名单
|
||||||
|
if !this.isSubRequest && this.web.UserAgent != nil && this.web.UserAgent.IsOn {
|
||||||
|
if this.doCheckUserAgent() {
|
||||||
|
this.doEnd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 访问控制
|
// 访问控制
|
||||||
if !this.isSubRequest && this.web.Auth != nil && this.web.Auth.IsOn {
|
if !this.isSubRequest && this.web.Auth != nil && this.web.Auth.IsOn {
|
||||||
if this.doAuth() {
|
if this.doAuth() {
|
||||||
@@ -512,6 +529,16 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
|
|||||||
this.web.Auth = web.Auth
|
this.web.Auth = web.Auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// referers
|
||||||
|
if web.Referers != nil && (web.Referers.IsPrior || isTop) {
|
||||||
|
this.web.Referers = web.Referers
|
||||||
|
}
|
||||||
|
|
||||||
|
// user agent
|
||||||
|
if web.UserAgent != nil && (web.UserAgent.IsPrior || isTop) {
|
||||||
|
this.web.UserAgent = web.UserAgent
|
||||||
|
}
|
||||||
|
|
||||||
// request limit
|
// request limit
|
||||||
if web.RequestLimit != nil && (web.RequestLimit.IsPrior || isTop) {
|
if web.RequestLimit != nil && (web.RequestLimit.IsPrior || isTop) {
|
||||||
this.web.RequestLimit = web.RequestLimit
|
this.web.RequestLimit = web.RequestLimit
|
||||||
@@ -744,6 +771,8 @@ func (this *HTTPRequest) Format(source string) string {
|
|||||||
return strconv.FormatInt(this.requestFromTime.Unix(), 10)
|
return strconv.FormatInt(this.requestFromTime.Unix(), 10)
|
||||||
case "host":
|
case "host":
|
||||||
return this.ReqHost
|
return this.ReqHost
|
||||||
|
case "cname":
|
||||||
|
return this.ReqServer.CNameDomain
|
||||||
case "referer":
|
case "referer":
|
||||||
return this.RawReq.Referer()
|
return this.RawReq.Referer()
|
||||||
case "referer.host":
|
case "referer.host":
|
||||||
@@ -1117,6 +1146,8 @@ func (this *HTTPRequest) requestRemoteAddr(supportVar bool) string {
|
|||||||
|
|
||||||
// 获取请求的客户端地址列表
|
// 获取请求的客户端地址列表
|
||||||
func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
|
func (this *HTTPRequest) requestRemoteAddrs() (result []string) {
|
||||||
|
result = append(result, this.requestRemoteAddr(true))
|
||||||
|
|
||||||
// X-Forwarded-For
|
// X-Forwarded-For
|
||||||
var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For")
|
var forwardedFor = this.RawReq.Header.Get("X-Forwarded-For")
|
||||||
if len(forwardedFor) > 0 {
|
if len(forwardedFor) > 0 {
|
||||||
@@ -1443,8 +1474,6 @@ func (this *HTTPRequest) Close() {
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow 放行
|
// Allow 放行
|
||||||
@@ -1540,7 +1569,7 @@ func (this *HTTPRequest) processRequestHeaders(reqHeader http.Header) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 是否已删除
|
// 是否已删除
|
||||||
if this.web.ResponseHeaderPolicy.ContainsDeletedHeader(header.Name) {
|
if this.web.RequestHeaderPolicy.ContainsDeletedHeader(header.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1591,17 +1620,31 @@ func (this *HTTPRequest) fixRequestHeader(header http.Header) {
|
|||||||
header.Del(k)
|
header.Del(k)
|
||||||
k = strings.ReplaceAll(k, "-Websocket-", "-WebSocket-")
|
k = strings.ReplaceAll(k, "-Websocket-", "-WebSocket-")
|
||||||
header[k] = v
|
header[k] = v
|
||||||
} else if k == "Www-Authenticate" {
|
} else if strings.HasPrefix(k, "Sec-Ch") {
|
||||||
header.Del(k)
|
header.Del(k)
|
||||||
header["WWW-Authenticate"] = v
|
k = strings.ReplaceAll(k, "Sec-Ch-Ua", "Sec-CH-UA")
|
||||||
|
header[k] = v
|
||||||
|
} else {
|
||||||
|
switch k {
|
||||||
|
case "Www-Authenticate":
|
||||||
|
header.Del(k)
|
||||||
|
header["WWW-Authenticate"] = v
|
||||||
|
case "A-Im":
|
||||||
|
header.Del(k)
|
||||||
|
header["A-IM"] = v
|
||||||
|
case "Content-Md5":
|
||||||
|
header.Del(k)
|
||||||
|
header["Content-MD5"] = v
|
||||||
|
case "Sec-Gpc":
|
||||||
|
header.Del(k)
|
||||||
|
header["Content-GPC"] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理自定义Response Header
|
// 处理自定义Response Header
|
||||||
func (this *HTTPRequest) processResponseHeaders(statusCode int) {
|
func (this *HTTPRequest) processResponseHeaders(responseHeader http.Header, statusCode int) {
|
||||||
var responseHeader = this.writer.Header()
|
|
||||||
|
|
||||||
// 删除/添加/替换Header
|
// 删除/添加/替换Header
|
||||||
// TODO 实现AddTrailers
|
// TODO 实现AddTrailers
|
||||||
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {
|
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {
|
||||||
@@ -1664,6 +1707,36 @@ func (this *HTTPRequest) processResponseHeaders(statusCode int) {
|
|||||||
responseHeader[header.Name] = []string{headerValue}
|
responseHeader[header.Name] = []string{headerValue}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CORS
|
||||||
|
if this.web.ResponseHeaderPolicy.CORS != nil && this.web.ResponseHeaderPolicy.CORS.IsOn {
|
||||||
|
var corsConfig = this.web.ResponseHeaderPolicy.CORS
|
||||||
|
|
||||||
|
// Allow-Origin
|
||||||
|
if len(corsConfig.AllowOrigin) == 0 {
|
||||||
|
var origin = this.RawReq.Header.Get("Origin")
|
||||||
|
if len(origin) > 0 {
|
||||||
|
responseHeader.Set("Access-Control-Allow-Origin", origin)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseHeader.Set("Access-Control-Allow-Origin", corsConfig.AllowOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow-Methods
|
||||||
|
if len(corsConfig.AllowMethods) == 0 {
|
||||||
|
responseHeader.Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, HEAD, OPTIONS")
|
||||||
|
} else {
|
||||||
|
responseHeader.Set("Access-Control-Allow-Methods", strings.Join(corsConfig.AllowMethods, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max-Age
|
||||||
|
if corsConfig.MaxAge > 0 {
|
||||||
|
responseHeader.Set("Access-Control-Max-Age", types.String(corsConfig.MaxAge))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow-Credentials
|
||||||
|
responseHeader.Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSTS
|
// HSTS
|
||||||
@@ -1695,10 +1768,10 @@ func (this *HTTPRequest) bytePool(contentLength int64) *utils.BytePool {
|
|||||||
return utils.BytePool1k
|
return utils.BytePool1k
|
||||||
}
|
}
|
||||||
if contentLength < 32768 { // 32K
|
if contentLength < 32768 { // 32K
|
||||||
return utils.BytePool4k
|
return utils.BytePool16k
|
||||||
}
|
}
|
||||||
if contentLength < 131072 { // 128K
|
if contentLength < 131072 { // 128K
|
||||||
return utils.BytePool16k
|
return utils.BytePool32k
|
||||||
}
|
}
|
||||||
return utils.BytePool32k
|
return utils.BytePool32k
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ import (
|
|||||||
|
|
||||||
// 读取缓存
|
// 读取缓存
|
||||||
func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||||
|
// 需要动态Upgrade的不缓存
|
||||||
|
if len(this.RawReq.Header.Get("Upgrade")) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.cacheCanTryStale = false
|
this.cacheCanTryStale = false
|
||||||
|
|
||||||
var cachePolicy = this.ReqServer.HTTPCachePolicy
|
var cachePolicy = this.ReqServer.HTTPCachePolicy
|
||||||
@@ -155,7 +160,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
for _, subKey := range subKeys {
|
for _, subKey := range subKeys {
|
||||||
err := storage.Delete(subKey)
|
err := storage.Delete(subKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
|
remotelogs.ErrorServer("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +265,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !this.canIgnore(err) {
|
if !this.canIgnore(err) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: open cache failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: open cache failed: "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -290,37 +295,34 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pool = this.bytePool(fileSize)
|
|
||||||
var buf = pool.Get()
|
|
||||||
defer func() {
|
|
||||||
pool.Put(buf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 读取Header
|
// 读取Header
|
||||||
var headerBuf = []byte{}
|
var headerData = []byte{}
|
||||||
this.writer.SetSentHeaderBytes(reader.HeaderSize())
|
this.writer.SetSentHeaderBytes(reader.HeaderSize())
|
||||||
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) {
|
var headerPool = this.bytePool(reader.HeaderSize())
|
||||||
headerBuf = append(headerBuf, buf[:n]...)
|
var headerBuf = headerPool.Get()
|
||||||
|
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, err error) {
|
||||||
|
headerData = append(headerData, headerBuf[:n]...)
|
||||||
for {
|
for {
|
||||||
nIndex := bytes.Index(headerBuf, []byte{'\n'})
|
nIndex := bytes.Index(headerData, []byte{'\n'})
|
||||||
if nIndex >= 0 {
|
if nIndex >= 0 {
|
||||||
row := headerBuf[:nIndex]
|
row := headerData[:nIndex]
|
||||||
spaceIndex := bytes.Index(row, []byte{':'})
|
spaceIndex := bytes.Index(row, []byte{':'})
|
||||||
if spaceIndex <= 0 {
|
if spaceIndex <= 0 {
|
||||||
return false, errors.New("invalid header '" + string(row) + "'")
|
return false, errors.New("invalid header '" + string(row) + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
this.writer.Header().Set(string(row[:spaceIndex]), string(row[spaceIndex+1:]))
|
this.writer.Header().Set(string(row[:spaceIndex]), string(row[spaceIndex+1:]))
|
||||||
headerBuf = headerBuf[nIndex+1:]
|
headerData = headerData[nIndex+1:]
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
})
|
})
|
||||||
|
headerPool.Put(headerBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !this.canIgnore(err) {
|
if !this.canIgnore(err) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read header failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read header failed: "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -372,7 +374,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
// 支持 If-None-Match
|
// 支持 If-None-Match
|
||||||
if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
|
if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusNotModified)
|
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||||
this.addExpiresHeader(reader.ExpiresAt())
|
this.addExpiresHeader(reader.ExpiresAt())
|
||||||
this.writer.WriteHeader(http.StatusNotModified)
|
this.writer.WriteHeader(http.StatusNotModified)
|
||||||
this.isCached = true
|
this.isCached = true
|
||||||
@@ -384,7 +386,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
// 支持 If-Modified-Since
|
// 支持 If-Modified-Since
|
||||||
if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
|
if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusNotModified)
|
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||||
this.addExpiresHeader(reader.ExpiresAt())
|
this.addExpiresHeader(reader.ExpiresAt())
|
||||||
this.writer.WriteHeader(http.StatusNotModified)
|
this.writer.WriteHeader(http.StatusNotModified)
|
||||||
this.isCached = true
|
this.isCached = true
|
||||||
@@ -393,7 +395,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.processResponseHeaders(reader.Status())
|
this.processResponseHeaders(this.writer.Header(), reader.Status())
|
||||||
this.addExpiresHeader(reader.ExpiresAt())
|
this.addExpiresHeader(reader.ExpiresAt())
|
||||||
|
|
||||||
// 返回上级节点过期时间
|
// 返回上级节点过期时间
|
||||||
@@ -422,7 +424,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
if supportRange {
|
if supportRange {
|
||||||
if len(rangeHeader) > 0 {
|
if len(rangeHeader) > 0 {
|
||||||
if fileSize == 0 {
|
if fileSize == 0 {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -430,7 +432,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
if len(ranges) == 0 {
|
if len(ranges) == 0 {
|
||||||
ranges, ok = httpRequestParseRangeHeader(rangeHeader)
|
ranges, ok = httpRequestParseRangeHeader(rangeHeader)
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -439,7 +441,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
for k, r := range ranges {
|
for k, r := range ranges {
|
||||||
r2, ok := r.Convert(fileSize)
|
r2, ok := r.Convert(fileSize)
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -455,23 +457,26 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
respHeader.Set("Content-Length", strconv.FormatInt(ranges[0].Length(), 10))
|
respHeader.Set("Content-Length", strconv.FormatInt(ranges[0].Length(), 10))
|
||||||
this.writer.WriteHeader(http.StatusPartialContent)
|
this.writer.WriteHeader(http.StatusPartialContent)
|
||||||
|
|
||||||
err = reader.ReadBodyRange(buf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) {
|
var pool = this.bytePool(fileSize)
|
||||||
_, err = this.writer.Write(buf[:n])
|
var bodyBuf = pool.Get()
|
||||||
|
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) {
|
||||||
|
_, err = this.writer.Write(bodyBuf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errWritingToClient
|
return false, errWritingToClient
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
})
|
})
|
||||||
|
pool.Put(bodyBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.varMapping["cache.status"] = "MISS"
|
this.varMapping["cache.status"] = "MISS"
|
||||||
|
|
||||||
if err == caches.ErrInvalidRange {
|
if err == caches.ErrInvalidRange {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !this.canIgnore(err) {
|
if !this.canIgnore(err) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -508,16 +513,19 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := reader.ReadBodyRange(buf, r.Start(), r.End(), func(n int) (goNext bool, err error) {
|
var pool = this.bytePool(fileSize)
|
||||||
_, err = this.writer.Write(buf[:n])
|
var bodyBuf = pool.Get()
|
||||||
|
err := reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, err error) {
|
||||||
|
_, err = this.writer.Write(bodyBuf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errWritingToClient
|
return false, errWritingToClient
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
})
|
})
|
||||||
|
pool.Put(bodyBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !this.canIgnore(err) {
|
if !this.canIgnore(err) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -538,15 +546,18 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
this.writer.Prepare(resp, fileSize, reader.Status(), false)
|
this.writer.Prepare(resp, fileSize, reader.Status(), false)
|
||||||
this.writer.WriteHeader(reader.Status())
|
this.writer.WriteHeader(reader.Status())
|
||||||
|
|
||||||
|
var pool = this.bytePool(fileSize)
|
||||||
|
var bodyBuf = pool.Get()
|
||||||
if storage.CanSendfile() {
|
if storage.CanSendfile() {
|
||||||
if fp, canSendFile := this.writer.canSendfile(); canSendFile {
|
if fp, canSendFile := this.writer.canSendfile(); canSendFile {
|
||||||
this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, buf)
|
this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, bodyBuf)
|
||||||
} else {
|
} else {
|
||||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf)
|
||||||
}
|
}
|
||||||
|
pool.Put(bodyBuf)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -554,7 +565,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
this.varMapping["cache.status"] = "MISS"
|
this.varMapping["cache.status"] = "MISS"
|
||||||
|
|
||||||
if !this.canIgnore(err) {
|
if !this.canIgnore(err) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read body failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read body failed: "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -617,7 +628,14 @@ func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key s
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// 检查范围
|
// 检查范围
|
||||||
|
//const maxFirstSpan = 16 << 20 // TODO 可以在缓存策略中设置此值
|
||||||
for index, r := range ranges {
|
for index, r := range ranges {
|
||||||
|
// 没有指定结束位置时,自动指定一个
|
||||||
|
/**if r.Start() >= 0 && r.End() == -1 {
|
||||||
|
if partialReader.MaxLength() > r.Start()+maxFirstSpan {
|
||||||
|
r[1] = r.Start() + maxFirstSpan
|
||||||
|
}
|
||||||
|
}**/
|
||||||
r1, ok := r.Convert(partialReader.MaxLength())
|
r1, ok := r.Convert(partialReader.MaxLength())
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ const httpStatusPageTemplate = `<!DOCTYPE html>
|
|||||||
</html>`
|
</html>`
|
||||||
|
|
||||||
func (this *HTTPRequest) write404() {
|
func (this *HTTPRequest) write404() {
|
||||||
this.writeCode(http.StatusNotFound)
|
this.writeCode(http.StatusNotFound, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *HTTPRequest) writeCode(statusCode int) {
|
func (this *HTTPRequest) writeCode(statusCode int, enMessage string, zhMessage string) {
|
||||||
if this.doPage(statusCode) {
|
if this.doPage(statusCode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -42,12 +42,22 @@ func (this *HTTPRequest) writeCode(statusCode int) {
|
|||||||
case "requestId":
|
case "requestId":
|
||||||
return this.requestId
|
return this.requestId
|
||||||
case "message":
|
case "message":
|
||||||
return "" // 空
|
var acceptLanguages = this.RawReq.Header.Get("Accept-Language")
|
||||||
|
if len(acceptLanguages) > 0 {
|
||||||
|
var index = strings.Index(acceptLanguages, ",")
|
||||||
|
if index > 0 {
|
||||||
|
var firstLanguage = acceptLanguages[:index]
|
||||||
|
if firstLanguage == "zh-CN" {
|
||||||
|
return zhMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return enMessage
|
||||||
}
|
}
|
||||||
return "${" + varName + "}"
|
return "${" + varName + "}"
|
||||||
})
|
})
|
||||||
|
|
||||||
this.processResponseHeaders(statusCode)
|
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||||
this.writer.WriteHeader(statusCode)
|
this.writer.WriteHeader(statusCode)
|
||||||
|
|
||||||
_, _ = this.writer.Write([]byte(pageContent))
|
_, _ = this.writer.Write([]byte(pageContent))
|
||||||
@@ -100,7 +110,7 @@ func (this *HTTPRequest) write50x(err error, statusCode int, enMessage string, z
|
|||||||
return "${" + varName + "}"
|
return "${" + varName + "}"
|
||||||
})
|
})
|
||||||
|
|
||||||
this.processResponseHeaders(statusCode)
|
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||||
this.writer.WriteHeader(statusCode)
|
this.writer.WriteHeader(statusCode)
|
||||||
|
|
||||||
_, _ = this.writer.Write([]byte(pageContent))
|
_, _ = this.writer.Write([]byte(pageContent))
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
|||||||
|
|
||||||
// 响应Header
|
// 响应Header
|
||||||
this.writer.AddHeaders(resp.Header)
|
this.writer.AddHeaders(resp.Header)
|
||||||
this.processResponseHeaders(resp.StatusCode)
|
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||||
|
|
||||||
// 准备
|
// 准备
|
||||||
this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)
|
this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -13,7 +17,7 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
|||||||
if this.web.MergeSlashes {
|
if this.web.MergeSlashes {
|
||||||
urlPath = utils.CleanPath(urlPath)
|
urlPath = utils.CleanPath(urlPath)
|
||||||
}
|
}
|
||||||
fullURL := this.requestScheme() + "://" + this.ReqHost + urlPath
|
var fullURL = this.requestScheme() + "://" + this.ReqHost + urlPath
|
||||||
for _, u := range this.web.HostRedirects {
|
for _, u := range this.web.HostRedirects {
|
||||||
if !u.IsOn {
|
if !u.IsOn {
|
||||||
continue
|
continue
|
||||||
@@ -21,11 +25,50 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
|||||||
if !u.MatchRequest(this.Format) {
|
if !u.MatchRequest(this.Format) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if u.MatchPrefix { // 匹配前缀
|
if len(u.Type) == 0 || u.Type == serverconfigs.HTTPHostRedirectTypeURL {
|
||||||
if strings.HasPrefix(fullURL, u.BeforeURL) {
|
if u.MatchPrefix { // 匹配前缀
|
||||||
afterURL := u.AfterURL
|
if strings.HasPrefix(fullURL, u.BeforeURL) {
|
||||||
if u.KeepRequestURI {
|
afterURL := u.AfterURL
|
||||||
afterURL += this.RawReq.URL.RequestURI()
|
if u.KeepRequestURI {
|
||||||
|
afterURL += this.RawReq.URL.RequestURI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前后是否一致
|
||||||
|
if fullURL == afterURL {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Status <= 0 {
|
||||||
|
u.Status = http.StatusTemporaryRedirect
|
||||||
|
}
|
||||||
|
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||||
|
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if u.MatchRegexp { // 正则匹配
|
||||||
|
var reg = u.BeforeURLRegexp()
|
||||||
|
if reg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var matches = reg.FindStringSubmatch(fullURL)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var afterURL = u.AfterURL
|
||||||
|
for i, match := range matches {
|
||||||
|
afterURL = strings.ReplaceAll(afterURL, "${"+strconv.Itoa(i)+"}", match)
|
||||||
|
}
|
||||||
|
|
||||||
|
var subNames = reg.SubexpNames()
|
||||||
|
if len(subNames) > 0 {
|
||||||
|
for _, subName := range subNames {
|
||||||
|
if len(subName) > 0 {
|
||||||
|
index := reg.SubexpIndex(subName)
|
||||||
|
if index > -1 {
|
||||||
|
afterURL = strings.ReplaceAll(afterURL, "${"+subName+"}", matches[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 前后是否一致
|
// 前后是否一致
|
||||||
@@ -33,69 +76,6 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Status <= 0 {
|
|
||||||
this.processResponseHeaders(http.StatusTemporaryRedirect)
|
|
||||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
|
|
||||||
} else {
|
|
||||||
this.processResponseHeaders(u.Status)
|
|
||||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if u.MatchRegexp { // 正则匹配
|
|
||||||
reg := u.BeforeURLRegexp()
|
|
||||||
if reg == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matches := reg.FindStringSubmatch(fullURL)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
afterURL := u.AfterURL
|
|
||||||
for i, match := range matches {
|
|
||||||
afterURL = strings.ReplaceAll(afterURL, "${"+strconv.Itoa(i)+"}", match)
|
|
||||||
}
|
|
||||||
|
|
||||||
subNames := reg.SubexpNames()
|
|
||||||
if len(subNames) > 0 {
|
|
||||||
for _, subName := range subNames {
|
|
||||||
if len(subName) > 0 {
|
|
||||||
index := reg.SubexpIndex(subName)
|
|
||||||
if index > -1 {
|
|
||||||
afterURL = strings.ReplaceAll(afterURL, "${"+subName+"}", matches[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 前后是否一致
|
|
||||||
if fullURL == afterURL {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.KeepArgs {
|
|
||||||
var qIndex = strings.Index(this.uri, "?")
|
|
||||||
if qIndex >= 0 {
|
|
||||||
afterURL += this.uri[qIndex:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Status <= 0 {
|
|
||||||
this.processResponseHeaders(http.StatusTemporaryRedirect)
|
|
||||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
|
|
||||||
} else {
|
|
||||||
this.processResponseHeaders(u.Status)
|
|
||||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else { // 精准匹配
|
|
||||||
if fullURL == u.RealBeforeURL() {
|
|
||||||
// 前后是否一致
|
|
||||||
if fullURL == u.AfterURL {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var afterURL = u.AfterURL
|
|
||||||
if u.KeepArgs {
|
if u.KeepArgs {
|
||||||
var qIndex = strings.Index(this.uri, "?")
|
var qIndex = strings.Index(this.uri, "?")
|
||||||
if qIndex >= 0 {
|
if qIndex >= 0 {
|
||||||
@@ -104,12 +84,127 @@ func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if u.Status <= 0 {
|
if u.Status <= 0 {
|
||||||
this.processResponseHeaders(http.StatusTemporaryRedirect)
|
u.Status = http.StatusTemporaryRedirect
|
||||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, http.StatusTemporaryRedirect)
|
|
||||||
} else {
|
|
||||||
this.processResponseHeaders(u.Status)
|
|
||||||
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
|
||||||
}
|
}
|
||||||
|
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||||
|
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||||
|
return true
|
||||||
|
} else { // 精准匹配
|
||||||
|
if fullURL == u.RealBeforeURL() {
|
||||||
|
// 前后是否一致
|
||||||
|
if fullURL == u.AfterURL {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var afterURL = u.AfterURL
|
||||||
|
if u.KeepArgs {
|
||||||
|
var qIndex = strings.Index(this.uri, "?")
|
||||||
|
if qIndex >= 0 {
|
||||||
|
afterURL += this.uri[qIndex:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Status <= 0 {
|
||||||
|
u.Status = http.StatusTemporaryRedirect
|
||||||
|
}
|
||||||
|
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||||
|
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if u.Type == serverconfigs.HTTPHostRedirectTypeDomain {
|
||||||
|
if len(u.DomainAfter) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqHost = this.ReqHost
|
||||||
|
|
||||||
|
// 忽略跳转前端口
|
||||||
|
if u.DomainBeforeIgnorePorts {
|
||||||
|
h, _, err := net.SplitHostPort(reqHost)
|
||||||
|
if err == nil && len(h) > 0 {
|
||||||
|
reqHost = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果跳转前后域名一致,则终止
|
||||||
|
if u.DomainAfter == reqHost {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var scheme = u.DomainAfterScheme
|
||||||
|
if len(scheme) == 0 {
|
||||||
|
scheme = this.requestScheme()
|
||||||
|
}
|
||||||
|
if u.DomainsAll || configutils.MatchDomains(u.DomainsBefore, reqHost) {
|
||||||
|
var afterURL = scheme + "://" + u.DomainAfter + urlPath
|
||||||
|
if fullURL == afterURL {
|
||||||
|
// 终止匹配
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if u.Status <= 0 {
|
||||||
|
u.Status = http.StatusTemporaryRedirect
|
||||||
|
}
|
||||||
|
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||||
|
|
||||||
|
// 参数
|
||||||
|
var qIndex = strings.Index(this.uri, "?")
|
||||||
|
if qIndex >= 0 {
|
||||||
|
afterURL += this.uri[qIndex:]
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if u.Type == serverconfigs.HTTPHostRedirectTypePort {
|
||||||
|
if u.PortAfter <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var scheme = u.PortAfterScheme
|
||||||
|
if len(scheme) == 0 {
|
||||||
|
scheme = this.requestScheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
reqHost, reqPort, _ := net.SplitHostPort(this.ReqHost)
|
||||||
|
if len(reqHost) == 0 {
|
||||||
|
reqHost = this.ReqHost
|
||||||
|
}
|
||||||
|
if len(reqPort) == 0 {
|
||||||
|
switch this.requestScheme() {
|
||||||
|
case "http":
|
||||||
|
reqPort = "80"
|
||||||
|
case "https":
|
||||||
|
reqPort = "443"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果跳转前后端口一致,则终止
|
||||||
|
if reqPort == types.String(u.PortAfter) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var containsPort = false
|
||||||
|
if u.PortsAll {
|
||||||
|
containsPort = true
|
||||||
|
} else {
|
||||||
|
containsPort = u.ContainsPort(types.Int(reqPort))
|
||||||
|
}
|
||||||
|
if containsPort {
|
||||||
|
var newReqHost = reqHost
|
||||||
|
if !((scheme == "http" && u.PortAfter == 80) || (scheme == "https" && u.PortAfter == 443)) {
|
||||||
|
newReqHost += ":" + types.String(u.PortAfter)
|
||||||
|
}
|
||||||
|
var afterURL = scheme + "://" + newReqHost + urlPath
|
||||||
|
if fullURL == afterURL {
|
||||||
|
// 终止匹配
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if u.Status <= 0 {
|
||||||
|
u.Status = http.StatusTemporaryRedirect
|
||||||
|
}
|
||||||
|
this.processResponseHeaders(this.writer.Header(), u.Status)
|
||||||
|
http.Redirect(this.RawWriter, this.RawReq, afterURL, u.Status)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func (this *HTTPRequest) doRequestLimit() (shouldStop bool) {
|
|||||||
// TODO 处理分片提交的内容
|
// TODO 处理分片提交的内容
|
||||||
if this.web.RequestLimit.MaxBodyBytes() > 0 &&
|
if this.web.RequestLimit.MaxBodyBytes() > 0 &&
|
||||||
this.RawReq.ContentLength > this.web.RequestLimit.MaxBodyBytes() {
|
this.RawReq.ContentLength > this.web.RequestLimit.MaxBodyBytes() {
|
||||||
this.writeCode(http.StatusRequestEntityTooLarge)
|
this.writeCode(http.StatusRequestEntityTooLarge, "", "")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ func (this *HTTPRequest) doRequestLimit() (shouldStop bool) {
|
|||||||
clientConn, ok := requestConn.(ClientConnInterface)
|
clientConn, ok := requestConn.(ClientConnInterface)
|
||||||
if ok && !clientConn.IsBound() {
|
if ok && !clientConn.IsBound() {
|
||||||
if !clientConn.Bind(this.ReqServer.Id, this.requestRemoteAddr(true), this.web.RequestLimit.MaxConns, this.web.RequestLimit.MaxConnsPerIP) {
|
if !clientConn.Bind(this.ReqServer.Id, this.requestRemoteAddr(true), this.web.RequestLimit.MaxConns, this.web.RequestLimit.MaxConnsPerIP) {
|
||||||
this.writeCode(http.StatusTooManyRequests)
|
this.writeCode(http.StatusTooManyRequests, "", "")
|
||||||
this.Close()
|
this.Close()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,27 +51,46 @@ func (this *HTTPRequest) log() {
|
|||||||
addr = addr[:index]
|
addr = addr[:index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var serverGlobalConfig = this.nodeConfig.GlobalServerConfig
|
||||||
|
|
||||||
// 请求Cookie
|
// 请求Cookie
|
||||||
var cookies = map[string]string{}
|
var cookies = map[string]string{}
|
||||||
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldCookie) {
|
var enableCookies = false
|
||||||
for _, cookie := range this.RawReq.Cookies() {
|
if serverGlobalConfig == nil || serverGlobalConfig.HTTPAccessLog.EnableCookies {
|
||||||
cookies[cookie.Name] = cookie.Value
|
enableCookies = true
|
||||||
|
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldCookie) {
|
||||||
|
for _, cookie := range this.RawReq.Cookies() {
|
||||||
|
cookies[cookie.Name] = cookie.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求Header
|
// 请求Header
|
||||||
var pbReqHeader = map[string]*pb.Strings{}
|
var pbReqHeader = map[string]*pb.Strings{}
|
||||||
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldHeader) {
|
if serverGlobalConfig == nil || serverGlobalConfig.HTTPAccessLog.EnableRequestHeaders {
|
||||||
for k, v := range this.RawReq.Header {
|
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldHeader) {
|
||||||
pbReqHeader[k] = &pb.Strings{Values: v}
|
// 是否只记录通用Header
|
||||||
|
var commonHeadersOnly = serverGlobalConfig != nil && serverGlobalConfig.HTTPAccessLog.CommonRequestHeadersOnly
|
||||||
|
|
||||||
|
for k, v := range this.RawReq.Header {
|
||||||
|
if commonHeadersOnly && !serverconfigs.IsCommonRequestHeader(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !enableCookies && k == "Cookie" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pbReqHeader[k] = &pb.Strings{Values: v}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应Header
|
// 响应Header
|
||||||
var pbResHeader = map[string]*pb.Strings{}
|
var pbResHeader = map[string]*pb.Strings{}
|
||||||
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldSentHeader) {
|
if serverGlobalConfig == nil || serverGlobalConfig.HTTPAccessLog.EnableResponseHeaders {
|
||||||
for k, v := range this.writer.Header() {
|
if ref == nil || ref.ContainsField(serverconfigs.HTTPAccessLogFieldSentHeader) {
|
||||||
pbResHeader[k] = &pb.Strings{Values: v}
|
for k, v := range this.writer.Header() {
|
||||||
|
pbResHeader[k] = &pb.Strings{Values: v}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +165,7 @@ func (this *HTTPRequest) log() {
|
|||||||
if this.origin != nil {
|
if this.origin != nil {
|
||||||
accessLog.OriginId = this.origin.Id
|
accessLog.OriginId = this.origin.Id
|
||||||
accessLog.OriginAddress = this.originAddr
|
accessLog.OriginAddress = this.originAddr
|
||||||
|
accessLog.OriginStatus = this.originStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求Body
|
// 请求Body
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (this *HTTPRequest) doMismatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据配置进行相应的处理
|
// 根据配置进行相应的处理
|
||||||
if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly {
|
if sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
|
||||||
// 检查cc
|
// 检查cc
|
||||||
// TODO 可以在管理端配置是否开启以及最多尝试次数
|
// TODO 可以在管理端配置是否开启以及最多尝试次数
|
||||||
if len(remoteIP) > 0 {
|
if len(remoteIP) > 0 {
|
||||||
@@ -46,7 +46,7 @@ func (this *HTTPRequest) doMismatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理当前连接
|
// 处理当前连接
|
||||||
var httpAllConfig = sharedNodeConfig.GlobalConfig.HTTPAll
|
var httpAllConfig = sharedNodeConfig.GlobalServerConfig.HTTPAll
|
||||||
var mismatchAction = httpAllConfig.DomainMismatchAction
|
var mismatchAction = httpAllConfig.DomainMismatchAction
|
||||||
if mismatchAction != nil && mismatchAction.Code == "page" {
|
if mismatchAction != nil && mismatchAction.Code == "page" {
|
||||||
if mismatchAction.Options != nil {
|
if mismatchAction.Options != nil {
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
|||||||
// 修改状态码
|
// 修改状态码
|
||||||
if page.NewStatus > 0 {
|
if page.NewStatus > 0 {
|
||||||
// 自定义响应Headers
|
// 自定义响应Headers
|
||||||
this.processResponseHeaders(page.NewStatus)
|
this.processResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||||
this.writer.Prepare(nil, stat.Size(), page.NewStatus, true)
|
this.writer.Prepare(nil, stat.Size(), page.NewStatus, true)
|
||||||
this.writer.WriteHeader(page.NewStatus)
|
this.writer.WriteHeader(page.NewStatus)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(status)
|
this.processResponseHeaders(this.writer.Header(), status)
|
||||||
this.writer.Prepare(nil, stat.Size(), status, true)
|
this.writer.Prepare(nil, stat.Size(), status, true)
|
||||||
this.writer.WriteHeader(status)
|
this.writer.WriteHeader(status)
|
||||||
}
|
}
|
||||||
@@ -99,11 +99,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
|||||||
// 修改状态码
|
// 修改状态码
|
||||||
if page.NewStatus > 0 {
|
if page.NewStatus > 0 {
|
||||||
// 自定义响应Headers
|
// 自定义响应Headers
|
||||||
this.processResponseHeaders(page.NewStatus)
|
this.processResponseHeaders(this.writer.Header(), page.NewStatus)
|
||||||
this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true)
|
this.writer.Prepare(nil, int64(len(content)), page.NewStatus, true)
|
||||||
this.writer.WriteHeader(page.NewStatus)
|
this.writer.WriteHeader(page.NewStatus)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(status)
|
this.processResponseHeaders(this.writer.Header(), status)
|
||||||
this.writer.Prepare(nil, int64(len(content)), status, true)
|
this.writer.Prepare(nil, int64(len(content)), status, true)
|
||||||
this.writer.WriteHeader(status)
|
this.writer.WriteHeader(status)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func (this *HTTPRequest) doPlanExpires() {
|
|||||||
this.tags = append(this.tags, "plan")
|
this.tags = append(this.tags, "plan")
|
||||||
|
|
||||||
var statusCode = http.StatusNotFound
|
var statusCode = http.StatusNotFound
|
||||||
this.processResponseHeaders(statusCode)
|
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||||
|
|
||||||
this.writer.WriteHeader(statusCode)
|
this.writer.WriteHeader(statusCode)
|
||||||
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))
|
_, _ = this.writer.WriteString(this.Format(serverconfigs.DefaultPlanExpireNoticePageBody))
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
|
|||||||
}
|
}
|
||||||
|
|
||||||
newURL := "https://" + host + this.RawReq.RequestURI
|
newURL := "https://" + host + this.RawReq.RequestURI
|
||||||
this.processResponseHeaders(statusCode)
|
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||||
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
|
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
50
internal/nodes/http_request_referers.go
Normal file
50
internal/nodes/http_request_referers.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (this *HTTPRequest) doCheckReferers() (shouldStop bool) {
|
||||||
|
if this.web.Referers == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
|
||||||
|
|
||||||
|
var refererURL = this.RawReq.Header.Get("Referer")
|
||||||
|
if len(refererURL) == 0 {
|
||||||
|
if this.web.Referers.MatchDomain(this.ReqHost, "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tags = append(this.tags, "refererCheck")
|
||||||
|
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
|
||||||
|
this.writeCode(http.StatusForbidden, "The referer has been blocked.", "当前访问已被防盗链系统拦截。")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(refererURL)
|
||||||
|
if err != nil {
|
||||||
|
if this.web.Referers.MatchDomain(this.ReqHost, "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tags = append(this.tags, "refererCheck")
|
||||||
|
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
|
||||||
|
this.writeCode(http.StatusForbidden, "The referer has been blocked.", "当前访问已被防盗链系统拦截。")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !this.web.Referers.MatchDomain(this.ReqHost, u.Host) {
|
||||||
|
this.tags = append(this.tags, "refererCheck")
|
||||||
|
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
|
||||||
|
this.writeCode(http.StatusForbidden, "The referer has been blocked.", "当前访问已被防盗链系统拦截。")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -116,7 +116,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
// 处理Scheme
|
// 处理Scheme
|
||||||
if origin.Addr == nil {
|
if origin.Addr == nil {
|
||||||
err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
|
err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
|
||||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||||
this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true)
|
this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
var originHostIndex = strings.Index(originAddr, ":")
|
var originHostIndex = strings.Index(originAddr, ":")
|
||||||
if originHostIndex < 0 {
|
if originHostIndex < 0 {
|
||||||
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
|
var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port")
|
||||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
|
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error())
|
||||||
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
|
this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -240,14 +240,14 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
|
|
||||||
// 判断是否为Websocket请求
|
// 判断是否为Websocket请求
|
||||||
if this.RawReq.Header.Get("Upgrade") == "websocket" {
|
if this.RawReq.Header.Get("Upgrade") == "websocket" {
|
||||||
this.doWebsocket(requestHost)
|
shouldRetry = this.doWebsocket(requestHost, isLastRetry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取请求客户端
|
// 获取请求客户端
|
||||||
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
|
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
|
remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error())
|
||||||
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
|
this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
this.reverseProxy.ResetScheduling()
|
this.reverseProxy.ResetScheduling()
|
||||||
})
|
})
|
||||||
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+err.Error())
|
||||||
} else if httpErr.Err != context.Canceled {
|
} else if httpErr.Err != context.Canceled {
|
||||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||||
this.reverseProxy.ResetScheduling()
|
this.reverseProxy.ResetScheduling()
|
||||||
@@ -278,7 +278,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
}
|
}
|
||||||
|
|
||||||
if httpErr.Err != io.EOF {
|
if httpErr.Err != io.EOF {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -292,7 +292,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
this.write50x(err, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true)
|
||||||
}
|
}
|
||||||
if httpErr.Err != io.EOF {
|
if httpErr.Err != io.EOF {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 是否为客户端方面的错误
|
// 是否为客户端方面的错误
|
||||||
@@ -320,6 +320,11 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录相关数据
|
||||||
|
this.originStatus = int32(resp.StatusCode)
|
||||||
|
|
||||||
|
// 恢复源站状态
|
||||||
if !origin.IsOk {
|
if !origin.IsOk {
|
||||||
SharedOriginStateManager.Success(origin, func() {
|
SharedOriginStateManager.Success(origin, func() {
|
||||||
this.reverseProxy.ResetScheduling()
|
this.reverseProxy.ResetScheduling()
|
||||||
@@ -331,7 +336,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
if this.doWAFResponse(resp) {
|
if this.doWAFResponse(resp) {
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing Error (WAF): "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -341,7 +346,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
|
if len(this.web.Pages) > 0 && this.doPage(resp.StatusCode) {
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error (Page): "+err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -392,7 +397,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
|
|
||||||
// 响应Header
|
// 响应Header
|
||||||
this.writer.AddHeaders(resp.Header)
|
this.writer.AddHeaders(resp.Header)
|
||||||
this.processResponseHeaders(resp.StatusCode)
|
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||||
|
|
||||||
// 是否需要刷新
|
// 是否需要刷新
|
||||||
var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
|
var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
|
||||||
@@ -443,13 +448,13 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
|||||||
var closeErr = resp.Body.Close()
|
var closeErr = resp.Body.Close()
|
||||||
if closeErr != nil {
|
if closeErr != nil {
|
||||||
if !this.canIgnore(closeErr) {
|
if !this.canIgnore(closeErr) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error: "+closeErr.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error: "+closeErr.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
if !this.canIgnore(err) {
|
if !this.canIgnore(err) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Writing error: "+err.Error())
|
remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Writing error: "+err.Error())
|
||||||
this.addError(err)
|
this.addError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ func (this *HTTPRequest) doRewrite() (shouldShop bool) {
|
|||||||
// 跳转
|
// 跳转
|
||||||
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
|
if this.rewriteRule.Mode == serverconfigs.HTTPRewriteModeRedirect {
|
||||||
if this.rewriteRule.RedirectStatus > 0 {
|
if this.rewriteRule.RedirectStatus > 0 {
|
||||||
this.processResponseHeaders(this.rewriteRule.RedirectStatus)
|
this.processResponseHeaders(this.writer.Header(), this.rewriteRule.RedirectStatus)
|
||||||
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
|
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, this.rewriteRule.RedirectStatus)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(http.StatusTemporaryRedirect)
|
this.processResponseHeaders(this.writer.Header(), http.StatusTemporaryRedirect)
|
||||||
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
|
http.Redirect(this.writer, this.RawReq, this.rewriteReplace, http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
// 支持 If-None-Match
|
// 支持 If-None-Match
|
||||||
if this.requestHeader("If-None-Match") == eTag {
|
if this.requestHeader("If-None-Match") == eTag {
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusNotModified)
|
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||||
this.writer.WriteHeader(http.StatusNotModified)
|
this.writer.WriteHeader(http.StatusNotModified)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
// 支持 If-Modified-Since
|
// 支持 If-Modified-Since
|
||||||
if this.requestHeader("If-Modified-Since") == modifiedTime {
|
if this.requestHeader("If-Modified-Since") == modifiedTime {
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusNotModified)
|
this.processResponseHeaders(this.writer.Header(), http.StatusNotModified)
|
||||||
this.writer.WriteHeader(http.StatusNotModified)
|
this.writer.WriteHeader(http.StatusNotModified)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -253,14 +253,14 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
var contentRange = this.RawReq.Header.Get("Range")
|
var contentRange = this.RawReq.Header.Get("Range")
|
||||||
if len(contentRange) > 0 {
|
if len(contentRange) > 0 {
|
||||||
if fileSize == 0 {
|
if fileSize == 0 {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
set, ok := httpRequestParseRangeHeader(contentRange)
|
set, ok := httpRequestParseRangeHeader(contentRange)
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -269,7 +269,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
for k, r := range ranges {
|
for k, r := range ranges {
|
||||||
r2, ok := r.Convert(fileSize)
|
r2, ok := r.Convert(fileSize)
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusOK)
|
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||||
|
|
||||||
// 在Range请求中不能缓存
|
// 在Range请求中不能缓存
|
||||||
if len(ranges) > 0 {
|
if len(ranges) > 0 {
|
||||||
@@ -325,7 +325,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -377,7 +377,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ func (this *HTTPRequest) doShutdown() {
|
|||||||
if len(shutdown.URL) == 0 {
|
if len(shutdown.URL) == 0 {
|
||||||
// 自定义响应Headers
|
// 自定义响应Headers
|
||||||
if shutdown.Status > 0 {
|
if shutdown.Status > 0 {
|
||||||
this.processResponseHeaders(shutdown.Status)
|
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||||
this.writer.WriteHeader(shutdown.Status)
|
this.writer.WriteHeader(shutdown.Status)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(http.StatusOK)
|
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||||
this.writer.WriteHeader(http.StatusOK)
|
this.writer.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
_, err := this.writer.WriteString("The site have been shutdown.")
|
_, err := this.writer.WriteString("The site have been shutdown.")
|
||||||
@@ -59,10 +59,10 @@ func (this *HTTPRequest) doShutdown() {
|
|||||||
|
|
||||||
// 自定义响应Headers
|
// 自定义响应Headers
|
||||||
if shutdown.Status > 0 {
|
if shutdown.Status > 0 {
|
||||||
this.processResponseHeaders(shutdown.Status)
|
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||||
this.writer.WriteHeader(shutdown.Status)
|
this.writer.WriteHeader(shutdown.Status)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(http.StatusOK)
|
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||||
this.writer.WriteHeader(http.StatusOK)
|
this.writer.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
buf := utils.BytePool1k.Get()
|
buf := utils.BytePool1k.Get()
|
||||||
@@ -85,10 +85,10 @@ func (this *HTTPRequest) doShutdown() {
|
|||||||
} else if shutdown.BodyType == shared.BodyTypeHTML {
|
} else if shutdown.BodyType == shared.BodyTypeHTML {
|
||||||
// 自定义响应Headers
|
// 自定义响应Headers
|
||||||
if shutdown.Status > 0 {
|
if shutdown.Status > 0 {
|
||||||
this.processResponseHeaders(shutdown.Status)
|
this.processResponseHeaders(this.writer.Header(), shutdown.Status)
|
||||||
this.writer.WriteHeader(shutdown.Status)
|
this.writer.WriteHeader(shutdown.Status)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(http.StatusOK)
|
this.processResponseHeaders(this.writer.Header(), http.StatusOK)
|
||||||
this.writer.WriteHeader(http.StatusOK)
|
this.writer.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ func (this *HTTPRequest) doStat() {
|
|||||||
|
|
||||||
// 内置的统计
|
// 内置的统计
|
||||||
stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.ReqServer.Id, this.requestRemoteAddr(true), this.writer.SentBodyBytes(), this.isAttack)
|
stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.ReqServer.Id, this.requestRemoteAddr(true), this.writer.SentBodyBytes(), this.isAttack)
|
||||||
stats.SharedHTTPRequestStatManager.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent"))
|
stats.SharedHTTPRequestStatManager.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent"), this.remoteAddr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func (this *HTTPRequest) doTrafficLimit() {
|
|||||||
this.tags = append(this.tags, "bandwidth")
|
this.tags = append(this.tags, "bandwidth")
|
||||||
|
|
||||||
var statusCode = 509
|
var statusCode = 509
|
||||||
this.processResponseHeaders(statusCode)
|
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||||
|
|
||||||
this.writer.WriteHeader(statusCode)
|
this.writer.WriteHeader(statusCode)
|
||||||
if len(config.NoticePageBody) != 0 {
|
if len(config.NoticePageBody) != 0 {
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
|
|||||||
|
|
||||||
// Header
|
// Header
|
||||||
if statusCode <= 0 {
|
if statusCode <= 0 {
|
||||||
this.processResponseHeaders(resp.StatusCode)
|
this.processResponseHeaders(this.writer.Header(), resp.StatusCode)
|
||||||
} else {
|
} else {
|
||||||
this.processResponseHeaders(statusCode)
|
this.processResponseHeaders(this.writer.Header(), statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if supportVariables {
|
if supportVariables {
|
||||||
|
|||||||
24
internal/nodes/http_request_user_agent.go
Normal file
24
internal/nodes/http_request_user_agent.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (this *HTTPRequest) doCheckUserAgent() (shouldStop bool) {
|
||||||
|
if this.web.UserAgent == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
|
||||||
|
|
||||||
|
if !this.web.UserAgent.AllowRequest(this.RawReq) {
|
||||||
|
this.tags = append(this.tags, "userAgentCheck")
|
||||||
|
this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds)
|
||||||
|
this.writeCode(http.StatusForbidden, "The User-Agent has been blocked.", "当前访问已被UA名单拦截。")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -173,7 +173,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
|||||||
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
|
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
|
||||||
this.firewallPolicyId = firewallPolicy.Id
|
this.firewallPolicyId = firewallPolicy.Id
|
||||||
|
|
||||||
this.writeCode(http.StatusForbidden)
|
this.writeCode(http.StatusForbidden, "", "")
|
||||||
this.writer.Flush()
|
this.writer.Flush()
|
||||||
this.writer.Close()
|
this.writer.Close()
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
|||||||
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
|
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
|
||||||
this.firewallPolicyId = firewallPolicy.Id
|
this.firewallPolicyId = firewallPolicy.Id
|
||||||
|
|
||||||
this.writeCode(http.StatusForbidden)
|
this.writeCode(http.StatusForbidden, "", "")
|
||||||
this.writer.Flush()
|
this.writer.Flush()
|
||||||
this.writer.Close()
|
this.writer.Close()
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
|||||||
}
|
}
|
||||||
|
|
||||||
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
|
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
|
||||||
if forceLog && logRequestBody && hasRequestBody {
|
if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
|
||||||
this.wafHasRequestBody = true
|
this.wafHasRequestBody = true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -294,7 +294,7 @@ func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer)
|
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer)
|
||||||
if forceLog && logRequestBody && hasRequestBody {
|
if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
|
||||||
this.wafHasRequestBody = true
|
this.wafHasRequestBody = true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"io"
|
"io"
|
||||||
@@ -8,8 +10,36 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WebsocketResponseReader Websocket响应Reader
|
||||||
|
type WebsocketResponseReader struct {
|
||||||
|
rawReader io.Reader
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebsocketResponseReader(rawReader io.Reader) *WebsocketResponseReader {
|
||||||
|
return &WebsocketResponseReader{
|
||||||
|
rawReader: rawReader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *WebsocketResponseReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = this.rawReader.Read(p)
|
||||||
|
if n > 0 {
|
||||||
|
if len(this.buf) == 0 {
|
||||||
|
this.buf = make([]byte, n)
|
||||||
|
copy(this.buf, p[:n])
|
||||||
|
} else {
|
||||||
|
this.buf = append(this.buf, p[:n]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 处理Websocket请求
|
// 处理Websocket请求
|
||||||
func (this *HTTPRequest) doWebsocket(requestHost string) {
|
func (this *HTTPRequest) doWebsocket(requestHost string, isLastRetry bool) (shouldRetry bool) {
|
||||||
|
// 设置不缓存
|
||||||
|
this.web.Cache = nil
|
||||||
|
|
||||||
if this.web.WebsocketRef == nil || !this.web.WebsocketRef.IsOn || this.web.Websocket == nil || !this.web.Websocket.IsOn {
|
if this.web.WebsocketRef == nil || !this.web.WebsocketRef.IsOn || this.web.Websocket == nil || !this.web.Websocket.IsOn {
|
||||||
this.writer.WriteHeader(http.StatusForbidden)
|
this.writer.WriteHeader(http.StatusForbidden)
|
||||||
this.addError(errors.New("websocket have not been enabled yet"))
|
this.addError(errors.New("websocket have not been enabled yet"))
|
||||||
@@ -40,16 +70,26 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
|
|||||||
this.RawReq.Header.Set("Origin", newRequestOrigin)
|
this.RawReq.Header.Set("Origin", newRequestOrigin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前连接
|
||||||
|
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||||
|
if requestConn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接源站
|
||||||
// TODO 增加N次错误重试,重试的时候需要尝试不同的源站
|
// TODO 增加N次错误重试,重试的时候需要尝试不同的源站
|
||||||
originConn, _, err := OriginConnect(this.origin, this.requestServerPort(), this.RawReq.RemoteAddr, requestHost)
|
originConn, _, err := OriginConnect(this.origin, this.requestServerPort(), this.RawReq.RemoteAddr, requestHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.write50x(err, http.StatusBadGateway, "Failed to connect origin site", "源站连接失败", false)
|
if isLastRetry {
|
||||||
|
this.write50x(err, http.StatusBadGateway, "Failed to connect origin site", "源站连接失败", false)
|
||||||
|
}
|
||||||
|
|
||||||
// 增加失败次数
|
// 增加失败次数
|
||||||
SharedOriginStateManager.Fail(this.origin, requestHost, this.reverseProxy, func() {
|
SharedOriginStateManager.Fail(this.origin, requestHost, this.reverseProxy, func() {
|
||||||
this.reverseProxy.ResetScheduling()
|
this.reverseProxy.ResetScheduling()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
shouldRetry = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +109,11 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestClientConn, ok := requestConn.(ClientConnInterface)
|
||||||
|
if ok {
|
||||||
|
requestClientConn.SetIsWebsocket(true)
|
||||||
|
}
|
||||||
|
|
||||||
clientConn, _, err := this.writer.Hijack()
|
clientConn, _, err := this.writer.Hijack()
|
||||||
if err != nil || clientConn == nil {
|
if err != nil || clientConn == nil {
|
||||||
this.write50x(err, http.StatusInternalServerError, "Failed to get origin site connection", "获取源站连接失败", false)
|
this.write50x(err, http.StatusInternalServerError, "Failed to get origin site connection", "获取源站连接失败", false)
|
||||||
@@ -79,6 +124,58 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
// 读取第一个响应
|
||||||
|
var respReader = NewWebsocketResponseReader(originConn)
|
||||||
|
resp, err := http.ReadResponse(bufio.NewReader(respReader), this.RawReq)
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = clientConn.Close()
|
||||||
|
_ = originConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.processResponseHeaders(resp.Header, resp.StatusCode)
|
||||||
|
this.writer.statusCode = resp.StatusCode
|
||||||
|
|
||||||
|
// 将响应写回客户端
|
||||||
|
err = resp.Write(clientConn)
|
||||||
|
if err != nil {
|
||||||
|
if resp.Body != nil {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = clientConn.Close()
|
||||||
|
_ = originConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 剩余已经从源站读取的内容
|
||||||
|
var headerBytes = respReader.buf
|
||||||
|
var headerIndex = bytes.Index(headerBytes, []byte{'\r', '\n', '\r', '\n'}) // CRLF
|
||||||
|
if headerIndex > 0 {
|
||||||
|
var leftBytes = headerBytes[headerIndex+4:]
|
||||||
|
if len(leftBytes) > 0 {
|
||||||
|
_, err = clientConn.Write(leftBytes)
|
||||||
|
if err != nil {
|
||||||
|
if resp.Body != nil {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = clientConn.Close()
|
||||||
|
_ = originConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Body != nil {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制剩余的数据
|
||||||
var buf = utils.BytePool4k.Get()
|
var buf = utils.BytePool4k.Get()
|
||||||
defer utils.BytePool4k.Put(buf)
|
defer utils.BytePool4k.Put(buf)
|
||||||
for {
|
for {
|
||||||
@@ -98,4 +195,6 @@ func (this *HTTPRequest) doWebsocket(requestHost string) {
|
|||||||
_ = originConn.Close()
|
_ = originConn.Close()
|
||||||
}()
|
}()
|
||||||
_, _ = io.Copy(originConn, clientConn)
|
_, _ = io.Copy(originConn, clientConn)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func (this *HTTPWriter) Prepare(resp *http.Response, size int64, status int, ena
|
|||||||
this.req.web.RequestLimit != nil &&
|
this.req.web.RequestLimit != nil &&
|
||||||
this.req.web.RequestLimit.IsOn &&
|
this.req.web.RequestLimit.IsOn &&
|
||||||
this.req.web.RequestLimit.OutBandwidthPerConnBytes() > 0 {
|
this.req.web.RequestLimit.OutBandwidthPerConnBytes() > 0 {
|
||||||
this.writer = writers.NewRateLimitWriter(this.writer, this.req.web.RequestLimit.OutBandwidthPerConnBytes())
|
this.writer = writers.NewRateLimitWriter(this.req.RawReq.Context(), this.writer, this.req.web.RequestLimit.OutBandwidthPerConnBytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -303,7 +303,19 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
if this.isPartial {
|
if this.isPartial {
|
||||||
cacheKey += caches.SuffixPartial
|
cacheKey += caches.SuffixPartial
|
||||||
}
|
}
|
||||||
cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), size, cacheRef.MaxSizeBytes(), this.isPartial)
|
|
||||||
|
// 待写入尺寸
|
||||||
|
var totalSize = size
|
||||||
|
if totalSize < 0 && this.isPartial {
|
||||||
|
var contentRange = resp.Header.Get("Content-Range")
|
||||||
|
if len(contentRange) > 0 {
|
||||||
|
_, partialTotalSize := httpRequestParseContentRangeHeader(contentRange)
|
||||||
|
if partialTotalSize > 0 {
|
||||||
|
totalSize = partialTotalSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), this.calculateHeaderLength(), totalSize, cacheRef.MaxSizeBytes(), this.isPartial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == caches.ErrEntityTooLarge && addStatusHeader {
|
if err == caches.ErrEntityTooLarge && addStatusHeader {
|
||||||
this.Header().Set("X-Cache", "BYPASS, entity too large")
|
this.Header().Set("X-Cache", "BYPASS, entity too large")
|
||||||
@@ -324,13 +336,19 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 写入Header
|
// 写入Header
|
||||||
|
var headerBuf = utils.SharedBufferPool.Get()
|
||||||
for k, v := range this.Header() {
|
for k, v := range this.Header() {
|
||||||
|
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, v1 := range v {
|
for _, v1 := range v {
|
||||||
if this.isPartial && k == "Content-Type" && strings.Contains(v1, "multipart/byteranges") {
|
if this.isPartial && k == "Content-Type" && strings.Contains(v1, "multipart/byteranges") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = cacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
_, err = headerBuf.Write([]byte(k + ":" + v1 + "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
utils.SharedBufferPool.Put(headerBuf)
|
||||||
|
|
||||||
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
||||||
_ = this.cacheWriter.Discard()
|
_ = this.cacheWriter.Discard()
|
||||||
this.cacheWriter = nil
|
this.cacheWriter = nil
|
||||||
@@ -338,6 +356,14 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_, err = cacheWriter.WriteHeader(headerBuf.Bytes())
|
||||||
|
utils.SharedBufferPool.Put(headerBuf)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
||||||
|
_ = this.cacheWriter.Discard()
|
||||||
|
this.cacheWriter = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if this.isPartial {
|
if this.isPartial {
|
||||||
// content-range
|
// content-range
|
||||||
@@ -558,6 +584,11 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分区内容不压缩,防止读取失败
|
||||||
|
if !this.compressionConfig.EnablePartialContent && this.StatusCode() == http.StatusPartialContent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if this.compressionConfig.Level <= 0 {
|
if this.compressionConfig.Level <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -627,16 +658,21 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
|||||||
cacheKey += this.cacheReaderSuffix
|
cacheKey += this.cacheReaderSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
compressionCacheWriter, err := this.cacheStorage.OpenWriter(cacheKey+caches.SuffixCompression+compressionEncoding, expiredAt, this.StatusCode(), -1, cacheRef.MaxSizeBytes(), false)
|
compressionCacheWriter, err := this.cacheStorage.OpenWriter(cacheKey+caches.SuffixCompression+compressionEncoding, expiredAt, this.StatusCode(), this.calculateHeaderLength(), -1, cacheRef.MaxSizeBytes(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入Header
|
// 写入Header
|
||||||
|
var headerBuffer = utils.SharedBufferPool.Get()
|
||||||
for k, v := range this.Header() {
|
for k, v := range this.Header() {
|
||||||
|
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, v1 := range v {
|
for _, v1 := range v {
|
||||||
_, err = compressionCacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
_, err = headerBuffer.Write([]byte(k + ":" + v1 + "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
utils.SharedBufferPool.Put(headerBuffer)
|
||||||
remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error())
|
remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error())
|
||||||
_ = compressionCacheWriter.Discard()
|
_ = compressionCacheWriter.Discard()
|
||||||
compressionCacheWriter = nil
|
compressionCacheWriter = nil
|
||||||
@@ -645,6 +681,15 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = compressionCacheWriter.WriteHeader(headerBuffer.Bytes())
|
||||||
|
utils.SharedBufferPool.Put(headerBuffer)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error())
|
||||||
|
_ = compressionCacheWriter.Discard()
|
||||||
|
compressionCacheWriter = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if compressionCacheWriter != nil {
|
if compressionCacheWriter != nil {
|
||||||
this.compressionCacheWriter = compressionCacheWriter
|
this.compressionCacheWriter = compressionCacheWriter
|
||||||
var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter)
|
var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter)
|
||||||
@@ -942,10 +987,14 @@ func (this *HTTPWriter) finishWebP() {
|
|||||||
expiredAt = this.cacheWriter.ExpiredAt()
|
expiredAt = this.cacheWriter.ExpiredAt()
|
||||||
}
|
}
|
||||||
|
|
||||||
webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), -1, -1, false)
|
webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), -1, -1, -1, false)
|
||||||
if webpCacheWriter != nil {
|
if webpCacheWriter != nil {
|
||||||
// 写入Header
|
// 写入Header
|
||||||
for k, v := range this.Header() {
|
for k, v := range this.Header() {
|
||||||
|
if k == "Set-Cookie" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 这里是原始的数据,不需要内容编码
|
// 这里是原始的数据,不需要内容编码
|
||||||
if k == "Content-Encoding" || k == "Transfer-Encoding" {
|
if k == "Content-Encoding" || k == "Transfer-Encoding" {
|
||||||
continue
|
continue
|
||||||
@@ -1157,3 +1206,16 @@ func (this *HTTPWriter) finishRequest() {
|
|||||||
_ = this.rawReader.Close()
|
_ = this.rawReader.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算Header长度
|
||||||
|
func (this *HTTPWriter) calculateHeaderLength() (result int) {
|
||||||
|
for k, v := range this.Header() {
|
||||||
|
if k == "Set-Cookie" || (this.isPartial && k == "Content-Range") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, v1 := range v {
|
||||||
|
result += len(k) + 1 /**:**/ + len(v1) + 1 /**\n**/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
group *serverconfigs.ServerAddressGroup
|
group *serverconfigs.ServerAddressGroup
|
||||||
isListening bool
|
listener ListenerInterface // 监听器
|
||||||
listener ListenerInterface // 监听器
|
|
||||||
|
|
||||||
locker sync.RWMutex
|
locker sync.RWMutex
|
||||||
}
|
}
|
||||||
@@ -118,18 +120,64 @@ func (this *Listener) listenTCP() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *Listener) listenUDP() error {
|
func (this *Listener) listenUDP() error {
|
||||||
listener, err := this.createUDPListener()
|
var addr = this.group.Addr()
|
||||||
|
|
||||||
|
var ipv4PacketListener *ipv4.PacketConn
|
||||||
|
var ipv6PacketListener *ipv6.PacketConn
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(host) == 0 {
|
||||||
|
// ipv4
|
||||||
|
ipv4Listener, err := this.createUDPIPv4Listener()
|
||||||
|
if err == nil {
|
||||||
|
ipv4PacketListener = ipv4.NewPacketConn(ipv4Listener)
|
||||||
|
} else {
|
||||||
|
remotelogs.Error("LISTENER", "create udp ipv4 listener '"+addr+"': "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipv6
|
||||||
|
ipv6Listener, err := this.createUDPIPv6Listener()
|
||||||
|
if err == nil {
|
||||||
|
ipv6PacketListener = ipv6.NewPacketConn(ipv6Listener)
|
||||||
|
} else {
|
||||||
|
remotelogs.Error("LISTENER", "create udp ipv6 listener '"+addr+"': "+err.Error())
|
||||||
|
}
|
||||||
|
} else if strings.Contains(host, ":") { // ipv6
|
||||||
|
ipv6Listener, err := this.createUDPIPv6Listener()
|
||||||
|
if err == nil {
|
||||||
|
ipv6PacketListener = ipv6.NewPacketConn(ipv6Listener)
|
||||||
|
} else {
|
||||||
|
remotelogs.Error("LISTENER", "create udp ipv6 listener '"+addr+"': "+err.Error())
|
||||||
|
}
|
||||||
|
} else { // ipv4
|
||||||
|
ipv4Listener, err := this.createUDPIPv4Listener()
|
||||||
|
if err == nil {
|
||||||
|
ipv4PacketListener = ipv4.NewPacketConn(ipv4Listener)
|
||||||
|
} else {
|
||||||
|
remotelogs.Error("LISTENER", "create udp ipv4 listener '"+addr+"': "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
events.OnKey(events.EventQuit, this, func() {
|
events.OnKey(events.EventQuit, this, func() {
|
||||||
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
|
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
|
||||||
_ = listener.Close()
|
|
||||||
|
if ipv4PacketListener != nil {
|
||||||
|
_ = ipv4PacketListener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6PacketListener != nil {
|
||||||
|
_ = ipv6PacketListener.Close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.listener = &UDPListener{
|
this.listener = &UDPListener{
|
||||||
BaseListener: BaseListener{Group: this.group},
|
BaseListener: BaseListener{Group: this.group},
|
||||||
Listener: listener,
|
IPv4Listener: ipv4PacketListener,
|
||||||
|
IPv6Listener: ipv6PacketListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
@@ -168,12 +216,20 @@ func (this *Listener) createTCPListener() (net.Listener, error) {
|
|||||||
return listenConfig.Listen(context.Background(), "tcp", this.group.Addr())
|
return listenConfig.Listen(context.Background(), "tcp", this.group.Addr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建UDP监听器
|
// 创建UDP IPv4监听器
|
||||||
func (this *Listener) createUDPListener() (*net.UDPConn, error) {
|
func (this *Listener) createUDPIPv4Listener() (*net.UDPConn, error) {
|
||||||
// TODO 将来支持udp4/udp6
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", this.group.Addr())
|
addr, err := net.ResolveUDPAddr("udp", this.group.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return net.ListenUDP("udp", addr)
|
return net.ListenUDP("udp4", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建UDP监听器
|
||||||
|
func (this *Listener) createUDPIPv6Listener() (*net.UDPConn, error) {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", this.group.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net.ListenUDP("udp6", addr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package nodes
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/iwind/TeaGo/lists"
|
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseListener struct {
|
type BaseListener struct {
|
||||||
@@ -35,7 +36,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
|||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
Certificates: nil,
|
Certificates: nil,
|
||||||
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
||||||
tlsPolicy, _, err := this.matchSSL(clientInfo.ServerName)
|
tlsPolicy, _, err := this.matchSSL(this.helloServerName(clientInfo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
|||||||
return tlsPolicy.TLSConfig(), nil
|
return tlsPolicy.TLSConfig(), nil
|
||||||
},
|
},
|
||||||
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
||||||
tlsPolicy, cert, err := this.matchSSL(clientInfo.ServerName)
|
tlsPolicy, cert, err := this.matchSSL(this.helloServerName(clientInfo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -75,7 +76,7 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
|
|||||||
// 如果域名为空,则取第一个
|
// 如果域名为空,则取第一个
|
||||||
// 通常域名为空是因为是直接通过IP访问的
|
// 通常域名为空是因为是直接通过IP访问的
|
||||||
if len(domain) == 0 {
|
if len(domain) == 0 {
|
||||||
if group.IsHTTPS() && sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly {
|
if group.IsHTTPS() && sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly {
|
||||||
return nil, nil, errors.New("no tls server name matched")
|
return nil, nil, errors.New("no tls server name matched")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,19 +132,19 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchDomainStrictly = sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly
|
var matchDomainStrictly = sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly
|
||||||
|
|
||||||
if sharedNodeConfig.GlobalConfig != nil &&
|
if sharedNodeConfig.GlobalServerConfig != nil &&
|
||||||
len(sharedNodeConfig.GlobalConfig.HTTPAll.DefaultDomain) > 0 &&
|
len(sharedNodeConfig.GlobalServerConfig.HTTPAll.DefaultDomain) > 0 &&
|
||||||
(!matchDomainStrictly || lists.ContainsString(sharedNodeConfig.GlobalConfig.HTTPAll.AllowMismatchDomains, name)) {
|
(!matchDomainStrictly || configutils.MatchDomains(sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowMismatchDomains, name) || (sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowNodeIP && net.ParseIP(name) != nil)) {
|
||||||
defaultDomain := sharedNodeConfig.GlobalConfig.HTTPAll.DefaultDomain
|
var defaultDomain = sharedNodeConfig.GlobalServerConfig.HTTPAll.DefaultDomain
|
||||||
serverConfig, serverName = this.findNamedServerMatched(defaultDomain)
|
serverConfig, serverName = this.findNamedServerMatched(defaultDomain)
|
||||||
if serverConfig != nil {
|
if serverConfig != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matchDomainStrictly && !lists.ContainsString(sharedNodeConfig.GlobalConfig.HTTPAll.AllowMismatchDomains, name) {
|
if matchDomainStrictly && !configutils.MatchDomains(sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowMismatchDomains, name) && (!sharedNodeConfig.GlobalServerConfig.HTTPAll.AllowNodeIP || net.ParseIP(name) == nil) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +160,7 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
|
|||||||
|
|
||||||
// 严格查找域名
|
// 严格查找域名
|
||||||
func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *serverconfigs.ServerConfig, serverName string) {
|
func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *serverconfigs.ServerConfig, serverName string) {
|
||||||
group := this.Group
|
var group = this.Group
|
||||||
if group == nil {
|
if group == nil {
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
@@ -170,7 +171,7 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 是否严格匹配域名
|
// 是否严格匹配域名
|
||||||
matchDomainStrictly := sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly
|
var matchDomainStrictly = sharedNodeConfig.GlobalServerConfig != nil && sharedNodeConfig.GlobalServerConfig.HTTPAll.MatchDomainStrictly
|
||||||
|
|
||||||
// 如果只有一个server,则默认为这个
|
// 如果只有一个server,则默认为这个
|
||||||
var currentServers = group.Servers()
|
var currentServers = group.Servers()
|
||||||
@@ -182,22 +183,17 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
|
|||||||
return nil, name
|
return nil, name
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用CNAME来查找服务
|
// 从Hello信息中获取服务名称
|
||||||
// TODO 防止单IP随机生成域名攻击
|
func (this *BaseListener) helloServerName(clientInfo *tls.ClientHelloInfo) string {
|
||||||
func (this *BaseListener) findServerWithCNAME(domain string) *serverconfigs.ServerConfig {
|
var serverName = clientInfo.ServerName
|
||||||
if !sharedNodeConfig.SupportCNAME {
|
if len(serverName) == 0 {
|
||||||
return nil
|
var localAddr = clientInfo.Conn.LocalAddr()
|
||||||
|
if localAddr != nil {
|
||||||
|
tcpAddr, ok := localAddr.(*net.TCPAddr)
|
||||||
|
if ok {
|
||||||
|
serverName = tcpAddr.IP.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return serverName
|
||||||
var realName = sharedCNAMEManager.Lookup(domain)
|
|
||||||
if len(realName) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
group := this.Group
|
|
||||||
if group == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return group.MatchServerCNAME(realName)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func TestBaseListener_FindServer(t *testing.T) {
|
|||||||
sharedNodeConfig = &nodeconfigs.NodeConfig{}
|
sharedNodeConfig = &nodeconfigs.NodeConfig{}
|
||||||
|
|
||||||
var listener = &BaseListener{}
|
var listener = &BaseListener{}
|
||||||
listener.Group = &serverconfigs.ServerAddressGroup{}
|
listener.Group = serverconfigs.NewServerAddressGroup("https://*:443")
|
||||||
for i := 0; i < 1_000_000; i++ {
|
for i := 0; i < 1_000_000; i++ {
|
||||||
var server = &serverconfigs.ServerConfig{
|
var server = &serverconfigs.ServerConfig{
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
|
|
||||||
var httpErrorLogger = log.New(io.Discard, "", 0)
|
var httpErrorLogger = log.New(io.Discard, "", 0)
|
||||||
|
|
||||||
|
const HTTPIdleTimeout = 75 * time.Second
|
||||||
|
|
||||||
type contextKey struct {
|
type contextKey struct {
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
@@ -43,16 +45,12 @@ func (this *HTTPListener) Serve() error {
|
|||||||
this.httpServer = &http.Server{
|
this.httpServer = &http.Server{
|
||||||
Addr: this.addr,
|
Addr: this.addr,
|
||||||
Handler: this,
|
Handler: this,
|
||||||
ReadTimeout: 1 * time.Hour, // TODO 改成可以配置
|
ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
|
||||||
ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
|
IdleTimeout: HTTPIdleTimeout, // TODO 改成可以配置
|
||||||
WriteTimeout: 1 * time.Hour, // TODO 改成可以配置
|
|
||||||
IdleTimeout: 75 * time.Second, // TODO 改成可以配置
|
|
||||||
ConnState: func(conn net.Conn, state http.ConnState) {
|
ConnState: func(conn net.Conn, state http.ConnState) {
|
||||||
switch state {
|
switch state {
|
||||||
case http.StateNew:
|
case http.StateNew:
|
||||||
atomic.AddInt64(&this.countActiveConnections, 1)
|
atomic.AddInt64(&this.countActiveConnections, 1)
|
||||||
case http.StateActive, http.StateIdle, http.StateHijacked:
|
|
||||||
// Nothing to do
|
|
||||||
case http.StateClosed:
|
case http.StateClosed:
|
||||||
atomic.AddInt64(&this.countActiveConnections, -1)
|
atomic.AddInt64(&this.countActiveConnections, -1)
|
||||||
}
|
}
|
||||||
@@ -116,8 +114,14 @@ func (this *HTTPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
|||||||
|
|
||||||
// ServerHTTP 处理HTTP请求
|
// ServerHTTP 处理HTTP请求
|
||||||
func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
|
func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
|
||||||
|
// 不支持Connect
|
||||||
|
if rawReq.Method == http.MethodConnect {
|
||||||
|
http.Error(rawWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 域名
|
// 域名
|
||||||
var reqHost = rawReq.Host
|
var reqHost = strings.ToLower(strings.TrimRight(rawReq.Host, "."))
|
||||||
|
|
||||||
// TLS域名
|
// TLS域名
|
||||||
if this.isIP(reqHost) {
|
if this.isIP(reqHost) {
|
||||||
@@ -175,6 +179,15 @@ func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查用户
|
||||||
|
if server != nil && server.UserId > 0 {
|
||||||
|
if !SharedUserManager.CheckUserServersIsEnabled(server.UserId) {
|
||||||
|
rawWriter.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = rawWriter.Write([]byte("The site owner is unavailable."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 包装新请求对象
|
// 包装新请求对象
|
||||||
var req = &HTTPRequest{
|
var req = &HTTPRequest{
|
||||||
RawReq: rawReq,
|
RawReq: rawReq,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
@@ -9,6 +8,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
@@ -213,15 +213,14 @@ func (this *ListenerManager) findProcessNameWithPort(isUdp bool, port string) st
|
|||||||
option = "u"
|
option = "u"
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd = exec.Command(path, "-"+option+"lpn", "sport = :"+port)
|
var cmd = executils.NewTimeoutCmd(10*time.Second, path, "-"+option+"lpn", "sport = :"+port)
|
||||||
var output = &bytes.Buffer{}
|
cmd.WithStdout()
|
||||||
cmd.Stdout = output
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(output.String())
|
var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(cmd.Stdout())
|
||||||
if len(matches) > 1 {
|
if len(matches) > 1 {
|
||||||
return matches[1]
|
return matches[1]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 是否已达到流量限制
|
// 是否已达到流量限制
|
||||||
if this.reachedTrafficLimit() {
|
if this.reachedTrafficLimit() || (server.UserId > 0 && !SharedUserManager.CheckUserServersIsEnabled(server.UserId)) {
|
||||||
// 关闭连接
|
// 关闭连接
|
||||||
tcpConn, ok := conn.(LingerConn)
|
tcpConn, ok := conn.(LingerConn)
|
||||||
if ok {
|
if ok {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"github.com/pires/go-proxyproto"
|
"github.com/pires/go-proxyproto"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -19,10 +21,57 @@ const (
|
|||||||
UDPConnLifeSeconds = 30
|
UDPConnLifeSeconds = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UDPPacketListener interface {
|
||||||
|
ReadFrom(b []byte) (n int, cm any, src net.Addr, err error)
|
||||||
|
WriteTo(b []byte, cm any, dst net.Addr) (n int, err error)
|
||||||
|
LocalAddr() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPIPv4Listener struct {
|
||||||
|
rawListener *ipv4.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPIPv4Listener(rawListener *ipv4.PacketConn) *UDPIPv4Listener {
|
||||||
|
return &UDPIPv4Listener{rawListener: rawListener}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPIPv4Listener) ReadFrom(b []byte) (n int, cm any, src net.Addr, err error) {
|
||||||
|
return this.rawListener.ReadFrom(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPIPv4Listener) WriteTo(b []byte, cm any, dst net.Addr) (n int, err error) {
|
||||||
|
return this.rawListener.WriteTo(b, cm.(*ipv4.ControlMessage), dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPIPv4Listener) LocalAddr() net.Addr {
|
||||||
|
return this.rawListener.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPIPv6Listener struct {
|
||||||
|
rawListener *ipv6.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPIPv6Listener(rawListener *ipv6.PacketConn) *UDPIPv6Listener {
|
||||||
|
return &UDPIPv6Listener{rawListener: rawListener}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPIPv6Listener) ReadFrom(b []byte) (n int, cm any, src net.Addr, err error) {
|
||||||
|
return this.rawListener.ReadFrom(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPIPv6Listener) WriteTo(b []byte, cm any, dst net.Addr) (n int, err error) {
|
||||||
|
return this.rawListener.WriteTo(b, cm.(*ipv6.ControlMessage), dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPIPv6Listener) LocalAddr() net.Addr {
|
||||||
|
return this.rawListener.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
type UDPListener struct {
|
type UDPListener struct {
|
||||||
BaseListener
|
BaseListener
|
||||||
|
|
||||||
Listener *net.UDPConn
|
IPv4Listener *ipv4.PacketConn
|
||||||
|
IPv6Listener *ipv6.PacketConn
|
||||||
|
|
||||||
connMap map[string]*UDPConn
|
connMap map[string]*UDPConn
|
||||||
connLocker sync.Mutex
|
connLocker sync.Mutex
|
||||||
@@ -36,6 +85,60 @@ type UDPListener struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *UDPListener) Serve() error {
|
func (this *UDPListener) Serve() error {
|
||||||
|
if this.Group == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var server = this.Group.FirstServer()
|
||||||
|
if server == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var serverId = server.Id
|
||||||
|
|
||||||
|
var wg = &sync.WaitGroup{}
|
||||||
|
wg.Add(2) // 2 = ipv4 + ipv6
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if this.IPv4Listener != nil {
|
||||||
|
err := this.IPv4Listener.SetControlMessage(ipv4.FlagDst, true)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv4 listener: "+err.Error(), "", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = this.servePacketListener(NewUDPIPv4Listener(this.IPv4Listener))
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv4 listener: "+err.Error(), "", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if this.IPv6Listener != nil {
|
||||||
|
err := this.IPv6Listener.SetControlMessage(ipv6.FlagDst, true)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv6 listener: "+err.Error(), "", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = this.servePacketListener(NewUDPIPv6Listener(this.IPv6Listener))
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.ServerError(serverId, "UDP_LISTENER", "can not serve ipv6 listener: "+err.Error(), "", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UDPListener) servePacketListener(listener UDPPacketListener) error {
|
||||||
// 获取分组端口
|
// 获取分组端口
|
||||||
var groupAddr = this.Group.Addr()
|
var groupAddr = this.Group.Addr()
|
||||||
var portIndex = strings.LastIndex(groupAddr, ":")
|
var portIndex = strings.LastIndex(groupAddr, ":")
|
||||||
@@ -67,7 +170,12 @@ func (this *UDPListener) Serve() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n, addr, err := this.Listener.ReadFrom(buffer)
|
// 检查用户状态
|
||||||
|
if firstServer.UserId > 0 && !SharedUserManager.CheckUserServersIsEnabled(firstServer.UserId) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, cm, clientAddr, err := listener.ReadFrom(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if this.isClosed {
|
if this.isClosed {
|
||||||
return nil
|
return nil
|
||||||
@@ -77,14 +185,14 @@ func (this *UDPListener) Serve() error {
|
|||||||
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
this.connLocker.Lock()
|
this.connLocker.Lock()
|
||||||
conn, ok := this.connMap[addr.String()]
|
conn, ok := this.connMap[clientAddr.String()]
|
||||||
this.connLocker.Unlock()
|
this.connLocker.Unlock()
|
||||||
if ok && !conn.IsOk() {
|
if ok && !conn.IsOk() {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
originConn, err := this.connectOrigin(firstServer.Id, this.reverseProxy, addr)
|
originConn, err := this.connectOrigin(firstServer.Id, this.reverseProxy, listener.LocalAddr(), clientAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("UDP_LISTENER", "unable to connect to origin server: "+err.Error())
|
remotelogs.Error("UDP_LISTENER", "unable to connect to origin server: "+err.Error())
|
||||||
continue
|
continue
|
||||||
@@ -93,9 +201,9 @@ func (this *UDPListener) Serve() error {
|
|||||||
remotelogs.Error("UDP_LISTENER", "unable to find a origin server")
|
remotelogs.Error("UDP_LISTENER", "unable to find a origin server")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conn = NewUDPConn(firstServer, addr, this.Listener, originConn.(*net.UDPConn))
|
conn = NewUDPConn(firstServer, clientAddr, listener, cm, originConn.(*net.UDPConn))
|
||||||
this.connLocker.Lock()
|
this.connLocker.Lock()
|
||||||
this.connMap[addr.String()] = conn
|
this.connMap[clientAddr.String()] = conn
|
||||||
this.connLocker.Unlock()
|
this.connLocker.Unlock()
|
||||||
}
|
}
|
||||||
_, _ = conn.Write(buffer[:n])
|
_, _ = conn.Write(buffer[:n])
|
||||||
@@ -117,7 +225,26 @@ func (this *UDPListener) Close() error {
|
|||||||
}
|
}
|
||||||
this.connLocker.Unlock()
|
this.connLocker.Unlock()
|
||||||
|
|
||||||
return this.Listener.Close()
|
var errorStrings = []string{}
|
||||||
|
if this.IPv4Listener != nil {
|
||||||
|
err := this.IPv4Listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
errorStrings = append(errorStrings, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.IPv6Listener != nil {
|
||||||
|
err := this.IPv6Listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
errorStrings = append(errorStrings, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errorStrings) > 0 {
|
||||||
|
return errors.New(errorStrings[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
||||||
@@ -132,7 +259,7 @@ func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
|||||||
this.reverseProxy = firstServer.ReverseProxy
|
this.reverseProxy = firstServer.ReverseProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfigs.ReverseProxyConfig, remoteAddr net.Addr) (conn net.Conn, err error) {
|
func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfigs.ReverseProxyConfig, localAddr net.Addr, remoteAddr net.Addr) (conn net.Conn, err error) {
|
||||||
if reverseProxy == nil {
|
if reverseProxy == nil {
|
||||||
return nil, errors.New("no reverse proxy config")
|
return nil, errors.New("no reverse proxy config")
|
||||||
}
|
}
|
||||||
@@ -181,12 +308,12 @@ func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfi
|
|||||||
if strings.Contains(remoteAddr.String(), "[") {
|
if strings.Contains(remoteAddr.String(), "[") {
|
||||||
transportProtocol = proxyproto.UDPv6
|
transportProtocol = proxyproto.UDPv6
|
||||||
}
|
}
|
||||||
header := proxyproto.Header{
|
var header = proxyproto.Header{
|
||||||
Version: byte(reverseProxy.ProxyProtocol.Version),
|
Version: byte(reverseProxy.ProxyProtocol.Version),
|
||||||
Command: proxyproto.PROXY,
|
Command: proxyproto.PROXY,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
SourceAddr: remoteAddr,
|
SourceAddr: remoteAddr,
|
||||||
DestinationAddr: this.Listener.LocalAddr(),
|
DestinationAddr: localAddr,
|
||||||
}
|
}
|
||||||
_, err = header.WriteTo(conn)
|
_, err = header.WriteTo(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -224,21 +351,21 @@ func (this *UDPListener) gcConns() {
|
|||||||
|
|
||||||
// UDPConn 自定义的UDP连接管理
|
// UDPConn 自定义的UDP连接管理
|
||||||
type UDPConn struct {
|
type UDPConn struct {
|
||||||
addr net.Addr
|
addr net.Addr
|
||||||
proxyConn net.Conn
|
proxyListener UDPPacketListener
|
||||||
serverConn net.Conn
|
serverConn net.Conn
|
||||||
activatedAt int64
|
activatedAt int64
|
||||||
isOk bool
|
isOk bool
|
||||||
isClosed bool
|
isClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *net.UDPConn, serverConn *net.UDPConn) *UDPConn {
|
func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyListener UDPPacketListener, cm any, serverConn *net.UDPConn) *UDPConn {
|
||||||
var conn = &UDPConn{
|
var conn = &UDPConn{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
proxyConn: proxyConn,
|
proxyListener: proxyListener,
|
||||||
serverConn: serverConn,
|
serverConn: serverConn,
|
||||||
activatedAt: time.Now().Unix(),
|
activatedAt: time.Now().Unix(),
|
||||||
isOk: true,
|
isOk: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计
|
// 统计
|
||||||
@@ -246,6 +373,14 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *ne
|
|||||||
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理ControlMessage
|
||||||
|
switch controlMessage := cm.(type) {
|
||||||
|
case *ipv4.ControlMessage:
|
||||||
|
controlMessage.Src = controlMessage.Dst
|
||||||
|
case *ipv6.ControlMessage:
|
||||||
|
controlMessage.Src = controlMessage.Dst
|
||||||
|
}
|
||||||
|
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
var buffer = utils.BytePool4k.Get()
|
var buffer = utils.BytePool4k.Get()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -256,7 +391,8 @@ func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *ne
|
|||||||
n, err := serverConn.Read(buffer)
|
n, err := serverConn.Read(buffer)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
conn.activatedAt = time.Now().Unix()
|
conn.activatedAt = time.Now().Unix()
|
||||||
_, writingErr := proxyConn.WriteTo(buffer[:n], addr)
|
|
||||||
|
_, writingErr := proxyListener.WriteTo(buffer[:n], cm, addr)
|
||||||
if writingErr != nil {
|
if writingErr != nil {
|
||||||
conn.isOk = false
|
conn.isOk = false
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||||
@@ -23,12 +25,13 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils/clock"
|
_ "github.com/TeaOSLab/EdgeNode/internal/utils/agents" // 引入Agent管理器
|
||||||
|
_ "github.com/TeaOSLab/EdgeNode/internal/utils/clock" // 触发时钟更新
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/jsonutils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||||
"github.com/andybalholm/brotli"
|
"github.com/andybalholm/brotli"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/logs"
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"github.com/iwind/gosock/pkg/gosock"
|
"github.com/iwind/gosock/pkg/gosock"
|
||||||
@@ -37,6 +40,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -58,18 +62,27 @@ type Node struct {
|
|||||||
sock *gosock.Sock
|
sock *gosock.Sock
|
||||||
locker sync.Mutex
|
locker sync.Mutex
|
||||||
|
|
||||||
maxCPU int32
|
oldMaxCPU int32
|
||||||
maxThreads int
|
oldMaxThreads int
|
||||||
timezone string
|
oldTimezone string
|
||||||
|
oldHTTPCachePolicies []*serverconfigs.HTTPCachePolicy
|
||||||
|
oldHTTPFirewallPolicies []*firewallconfigs.HTTPFirewallPolicy
|
||||||
|
oldFirewallActions []*firewallconfigs.FirewallActionConfig
|
||||||
|
oldMetricItems []*serverconfigs.MetricItemConfig
|
||||||
|
|
||||||
updatingServerMap map[int64]*serverconfigs.ServerConfig
|
updatingServerMap map[int64]*serverconfigs.ServerConfig
|
||||||
|
|
||||||
|
lastAPINodeVersion int64
|
||||||
|
lastAPINodeAddrs []string // 以前的API节点地址
|
||||||
|
|
||||||
|
lastTaskVersion int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNode() *Node {
|
func NewNode() *Node {
|
||||||
return &Node{
|
return &Node{
|
||||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||||
maxThreads: -1,
|
oldMaxThreads: -1,
|
||||||
maxCPU: -1,
|
oldMaxCPU: -1,
|
||||||
updatingServerMap: map[int64]*serverconfigs.ServerConfig{},
|
updatingServerMap: map[int64]*serverconfigs.ServerConfig{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +104,10 @@ func (this *Node) Test() error {
|
|||||||
|
|
||||||
// Start 启动
|
// Start 启动
|
||||||
func (this *Node) Start() {
|
func (this *Node) Start() {
|
||||||
|
// 设置netdns
|
||||||
|
// 这个需要放在所有网络访问的最前面
|
||||||
|
_ = os.Setenv("GODEBUG", "netdns=go")
|
||||||
|
|
||||||
_, ok := os.LookupEnv("EdgeDaemon")
|
_, ok := os.LookupEnv("EdgeDaemon")
|
||||||
if ok {
|
if ok {
|
||||||
remotelogs.Println("NODE", "start from daemon")
|
remotelogs.Println("NODE", "start from daemon")
|
||||||
@@ -166,11 +183,6 @@ func (this *Node) Start() {
|
|||||||
NewNodeStatusExecutor().Listen()
|
NewNodeStatusExecutor().Listen()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 同步时间
|
|
||||||
goman.New(func() {
|
|
||||||
clock.Start()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 读取配置
|
// 读取配置
|
||||||
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -190,7 +202,7 @@ func (this *Node) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sharedNodeConfig = nodeConfig
|
sharedNodeConfig = nodeConfig
|
||||||
this.onReload(nodeConfig)
|
this.onReload(nodeConfig, true)
|
||||||
|
|
||||||
// 发送事件
|
// 发送事件
|
||||||
events.Notify(events.EventLoaded)
|
events.Notify(events.EventLoaded)
|
||||||
@@ -205,9 +217,7 @@ func (this *Node) Start() {
|
|||||||
|
|
||||||
// 统计
|
// 统计
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
stats.SharedTrafficStatManager.Start(func() *nodeconfigs.NodeConfig {
|
stats.SharedTrafficStatManager.Start()
|
||||||
return sharedNodeConfig
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
stats.SharedHTTPRequestStatManager.Start()
|
stats.SharedHTTPRequestStatManager.Start()
|
||||||
@@ -313,8 +323,9 @@ func (this *Node) loop() error {
|
|||||||
return errors.New("create rpc client failed: " + err.Error())
|
return errors.New("create rpc client failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeCtx = rpcClient.Context()
|
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
|
||||||
tasksResp, err := rpcClient.NodeTaskRPC.FindNodeTasks(nodeCtx, &pb.FindNodeTasksRequest{})
|
Version: this.lastTaskVersion,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rpc.IsConnError(err) && !Tea.IsTesting() {
|
if rpc.IsConnError(err) && !Tea.IsTesting() {
|
||||||
return nil
|
return nil
|
||||||
@@ -322,139 +333,181 @@ func (this *Node) loop() error {
|
|||||||
return errors.New("read node tasks failed: " + err.Error())
|
return errors.New("read node tasks failed: " + err.Error())
|
||||||
}
|
}
|
||||||
for _, task := range tasksResp.NodeTasks {
|
for _, task := range tasksResp.NodeTasks {
|
||||||
switch task.Type {
|
err := this.execTask(rpcClient, task)
|
||||||
case "ipItemChanged":
|
if !this.finishTask(task.Id, task.Version, err) {
|
||||||
// 防止阻塞
|
// 防止失败的任务无法重试
|
||||||
select {
|
break
|
||||||
case iplibrary.IPListUpdateNotify <- true:
|
|
||||||
default:
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改为已同步
|
|
||||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
|
|
||||||
NodeTaskId: task.Id,
|
|
||||||
IsOk: true,
|
|
||||||
Error: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "configChanged":
|
|
||||||
if task.ServerId > 0 {
|
|
||||||
err = this.syncServerConfig(task.ServerId)
|
|
||||||
} else {
|
|
||||||
if !task.IsPrimary {
|
|
||||||
// 我们等等主节点配置准备完毕
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
err = this.syncConfig(task.Version)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
|
|
||||||
NodeTaskId: task.Id,
|
|
||||||
IsOk: false,
|
|
||||||
Error: err.Error(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
|
|
||||||
NodeTaskId: task.Id,
|
|
||||||
IsOk: true,
|
|
||||||
Error: "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "nodeVersionChanged":
|
|
||||||
if !sharedUpgradeManager.IsInstalling() {
|
|
||||||
goman.New(func() {
|
|
||||||
sharedUpgradeManager.Start()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case "scriptsChanged":
|
|
||||||
err = this.reloadCommonScripts()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("reload common scripts failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改为已同步
|
|
||||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
|
|
||||||
NodeTaskId: task.Id,
|
|
||||||
IsOk: true,
|
|
||||||
Error: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "nodeLevelChanged":
|
|
||||||
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(nodeCtx, &pb.FindNodeLevelInfoRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedNodeConfig.Level = levelInfoResp.Level
|
|
||||||
|
|
||||||
var parentNodes = map[int64][]*nodeconfigs.ParentNodeConfig{}
|
|
||||||
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
|
|
||||||
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("decode level info failed: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sharedNodeConfig.ParentNodes = parentNodes
|
|
||||||
|
|
||||||
// 修改为已同步
|
|
||||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
|
|
||||||
NodeTaskId: task.Id,
|
|
||||||
IsOk: true,
|
|
||||||
Error: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "ddosProtectionChanged":
|
|
||||||
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(nodeCtx, &pb.FindNodeDDoSProtectionRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(resp.DdosProtectionJSON) == 0 {
|
|
||||||
if sharedNodeConfig != nil {
|
|
||||||
sharedNodeConfig.DDoSProtection = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
|
|
||||||
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("decode DDoS protection config failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if sharedNodeConfig != nil {
|
|
||||||
sharedNodeConfig.DDoSProtection = ddosProtectionConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
err = firewalls.SharedDDoSProtectionManager.Apply(ddosProtectionConfig)
|
|
||||||
if err != nil {
|
|
||||||
// 不阻塞
|
|
||||||
remotelogs.Error("NODE", "apply DDoS protection failed: "+err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改为已同步
|
|
||||||
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(nodeCtx, &pb.ReportNodeTaskDoneRequest{
|
|
||||||
NodeTaskId: task.Id,
|
|
||||||
IsOk: true,
|
|
||||||
Error: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行任务
|
||||||
|
func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
|
||||||
|
switch task.Type {
|
||||||
|
case "ipItemChanged":
|
||||||
|
// 防止阻塞
|
||||||
|
select {
|
||||||
|
case iplibrary.IPListUpdateNotify <- true:
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
case "configChanged":
|
||||||
|
if task.ServerId > 0 {
|
||||||
|
return this.syncServerConfig(task.ServerId)
|
||||||
|
}
|
||||||
|
if !task.IsPrimary {
|
||||||
|
// 我们等等主节点配置准备完毕
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
return this.syncConfig(task.Version)
|
||||||
|
case "nodeVersionChanged":
|
||||||
|
if !sharedUpgradeManager.IsInstalling() {
|
||||||
|
goman.New(func() {
|
||||||
|
sharedUpgradeManager.Start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "scriptsChanged":
|
||||||
|
err := this.reloadCommonScripts()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("reload common scripts failed: " + err.Error())
|
||||||
|
}
|
||||||
|
case "nodeLevelChanged":
|
||||||
|
levelInfoResp, err := rpcClient.NodeRPC.FindNodeLevelInfo(rpcClient.Context(), &pb.FindNodeLevelInfoRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sharedNodeConfig != nil {
|
||||||
|
sharedNodeConfig.Level = levelInfoResp.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentNodes = map[int64][]*nodeconfigs.ParentNodeConfig{}
|
||||||
|
if len(levelInfoResp.ParentNodesMapJSON) > 0 {
|
||||||
|
err = json.Unmarshal(levelInfoResp.ParentNodesMapJSON, &parentNodes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("decode level info failed: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sharedNodeConfig != nil {
|
||||||
|
sharedNodeConfig.ParentNodes = parentNodes
|
||||||
|
}
|
||||||
|
case "ddosProtectionChanged":
|
||||||
|
resp, err := rpcClient.NodeRPC.FindNodeDDoSProtection(rpcClient.Context(), &pb.FindNodeDDoSProtectionRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(resp.DdosProtectionJSON) == 0 {
|
||||||
|
if sharedNodeConfig != nil {
|
||||||
|
sharedNodeConfig.DDoSProtection = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
|
||||||
|
err = json.Unmarshal(resp.DdosProtectionJSON, ddosProtectionConfig)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("decode DDoS protection config failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ddosProtectionConfig != nil && sharedNodeConfig != nil {
|
||||||
|
sharedNodeConfig.DDoSProtection = ddosProtectionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
err = firewalls.SharedDDoSProtectionManager.Apply(ddosProtectionConfig)
|
||||||
|
if err != nil {
|
||||||
|
// 不阻塞
|
||||||
|
remotelogs.Warn("NODE", "apply DDoS protection failed: "+err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case "globalServerConfigChanged":
|
||||||
|
resp, err := rpcClient.NodeRPC.FindNodeGlobalServerConfig(rpcClient.Context(), &pb.FindNodeGlobalServerConfigRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(resp.GlobalServerConfigJSON) > 0 {
|
||||||
|
var globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
|
||||||
|
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("decode global server config failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalServerConfig != nil {
|
||||||
|
err = globalServerConfig.Init()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("validate global server config failed: " + err.Error())
|
||||||
|
}
|
||||||
|
if sharedNodeConfig != nil {
|
||||||
|
sharedNodeConfig.GlobalServerConfig = globalServerConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "userServersStateChanged":
|
||||||
|
if task.UserId > 0 {
|
||||||
|
resp, err := rpcClient.UserRPC.CheckUserServersState(rpcClient.Context(), &pb.CheckUserServersStateRequest{UserId: task.UserId})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedUserManager.UpdateUserServersIsEnabled(task.UserId, resp.IsEnabled)
|
||||||
|
|
||||||
|
if resp.IsEnabled {
|
||||||
|
err = this.syncUserServersConfig(task.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记任务完成
|
||||||
|
func (this *Node) finishTask(taskId int64, taskVersion int64, taskErr error) (success bool) {
|
||||||
|
if taskId <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcClient, err := rpc.SharedRPC()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Debug("NODE", "create rpc client failed: "+err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOk = taskErr == nil
|
||||||
|
if isOk && taskVersion > this.lastTaskVersion {
|
||||||
|
this.lastTaskVersion = taskVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMsg = ""
|
||||||
|
if taskErr != nil {
|
||||||
|
errMsg = taskErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = rpcClient.NodeTaskRPC.ReportNodeTaskDone(rpcClient.Context(), &pb.ReportNodeTaskDoneRequest{
|
||||||
|
NodeTaskId: taskId,
|
||||||
|
IsOk: isOk,
|
||||||
|
Error: errMsg,
|
||||||
|
})
|
||||||
|
success = err == nil
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 连接错误不需要上报到服务中心
|
||||||
|
if rpc.IsConnError(err) {
|
||||||
|
remotelogs.Debug("NODE", "report task done failed: "+err.Error())
|
||||||
|
} else {
|
||||||
|
remotelogs.Error("NODE", "report task done failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
// 读取API配置
|
// 读取API配置
|
||||||
func (this *Node) syncConfig(taskVersion int64) error {
|
func (this *Node) syncConfig(taskVersion int64) error {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
@@ -483,10 +536,8 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取同步任务
|
// 获取同步任务
|
||||||
nodeCtx := rpcClient.Context()
|
|
||||||
|
|
||||||
// TODO 这里考虑只同步版本号有变更的
|
// TODO 这里考虑只同步版本号有变更的
|
||||||
configResp, err := rpcClient.NodeRPC.FindCurrentNodeConfig(nodeCtx, &pb.FindCurrentNodeConfigRequest{
|
configResp, err := rpcClient.NodeRPC.FindCurrentNodeConfig(rpcClient.Context(), &pb.FindCurrentNodeConfigRequest{
|
||||||
Version: -1, // 更新所有版本
|
Version: -1, // 更新所有版本
|
||||||
Compress: true,
|
Compress: true,
|
||||||
NodeTaskVersion: taskVersion,
|
NodeTaskVersion: taskVersion,
|
||||||
@@ -557,7 +608,7 @@ func (this *Node) syncConfig(taskVersion int64) error {
|
|||||||
remotelogs.Println("NODE", "loading config ...")
|
remotelogs.Println("NODE", "loading config ...")
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onReload(nodeConfig)
|
this.onReload(nodeConfig, true)
|
||||||
|
|
||||||
// 发送事件
|
// 发送事件
|
||||||
events.Notify(events.EventReload)
|
events.Notify(events.EventReload)
|
||||||
@@ -597,6 +648,36 @@ func (this *Node) syncServerConfig(serverId int64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步某个用户下的所有服务配置
|
||||||
|
func (this *Node) syncUserServersConfig(userId int64) error {
|
||||||
|
rpcClient, err := rpc.SharedRPC()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serverConfigsResp, err := rpcClient.ServerRPC.ComposeAllUserServersConfig(rpcClient.Context(), &pb.ComposeAllUserServersConfigRequest{
|
||||||
|
UserId: userId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(serverConfigsResp.ServersConfigJSON) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var serverConfigs = []*serverconfigs.ServerConfig{}
|
||||||
|
err = json.Unmarshal(serverConfigsResp.ServersConfigJSON, &serverConfigs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
this.locker.Lock()
|
||||||
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
|
for _, config := range serverConfigs {
|
||||||
|
this.updatingServerMap[config.Id] = config
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 启动同步计时器
|
// 启动同步计时器
|
||||||
func (this *Node) startSyncTimer() {
|
func (this *Node) startSyncTimer() {
|
||||||
// TODO 这个时间间隔可以自行设置
|
// TODO 这个时间间隔可以自行设置
|
||||||
@@ -658,12 +739,12 @@ func (this *Node) checkClusterConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logs.Println("[NODE]registering node to cluster ...")
|
remotelogs.Debug("NODE", "registering node to cluster ...")
|
||||||
resp, err := rpcClient.NodeRPC.RegisterClusterNode(rpcClient.ClusterContext(config.ClusterId, config.Secret), &pb.RegisterClusterNodeRequest{Name: HOSTNAME})
|
resp, err := rpcClient.NodeRPC.RegisterClusterNode(rpcClient.ClusterContext(config.ClusterId, config.Secret), &pb.RegisterClusterNodeRequest{Name: HOSTNAME})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logs.Println("[NODE]registered successfully")
|
remotelogs.Debug("NODE", "registered successfully")
|
||||||
|
|
||||||
// 写入到配置文件中
|
// 写入到配置文件中
|
||||||
if len(resp.Endpoints) == 0 {
|
if len(resp.Endpoints) == 0 {
|
||||||
@@ -671,8 +752,8 @@ func (this *Node) checkClusterConfig() error {
|
|||||||
}
|
}
|
||||||
var apiConfig = &configs.APIConfig{
|
var apiConfig = &configs.APIConfig{
|
||||||
RPC: struct {
|
RPC: struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||||
DisableUpdate bool `yaml:"disableUpdate"`
|
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||||
}{
|
}{
|
||||||
Endpoints: resp.Endpoints,
|
Endpoints: resp.Endpoints,
|
||||||
DisableUpdate: false,
|
DisableUpdate: false,
|
||||||
@@ -680,12 +761,12 @@ func (this *Node) checkClusterConfig() error {
|
|||||||
NodeId: resp.UniqueId,
|
NodeId: resp.UniqueId,
|
||||||
Secret: resp.Secret,
|
Secret: resp.Secret,
|
||||||
}
|
}
|
||||||
logs.Println("[NODE]writing 'configs/api.yaml' ...")
|
remotelogs.Debug("NODE", "writing 'configs/api.yaml' ...")
|
||||||
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
|
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logs.Println("[NODE]wrote 'configs/api.yaml' successfully")
|
remotelogs.Debug("NODE", "wrote 'configs/api.yaml' successfully")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -741,6 +822,7 @@ func (this *Node) listenSock() error {
|
|||||||
|
|
||||||
// 退出主进程
|
// 退出主进程
|
||||||
events.Notify(events.EventQuit)
|
events.Notify(events.EventQuit)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
utils.Exit()
|
utils.Exit()
|
||||||
case "quit":
|
case "quit":
|
||||||
_ = cmd.ReplyOk()
|
_ = cmd.ReplyOk()
|
||||||
@@ -798,16 +880,56 @@ func (this *Node) listenSock() error {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
case "conns":
|
case "conns":
|
||||||
var addrs = []string{}
|
var connMaps = []maps.Map{}
|
||||||
var connMap = conns.SharedMap.AllConns()
|
var connMap = conns.SharedMap.AllConns()
|
||||||
for _, conn := range connMap {
|
for _, conn := range connMap {
|
||||||
addrs = append(addrs, conn.RemoteAddr().String())
|
var createdAt int64
|
||||||
|
var lastReadAt int64
|
||||||
|
var lastWriteAt int64
|
||||||
|
var lastErrString = ""
|
||||||
|
clientConn, ok := conn.(*ClientConn)
|
||||||
|
if ok {
|
||||||
|
createdAt = clientConn.CreatedAt()
|
||||||
|
lastReadAt = clientConn.LastReadAt()
|
||||||
|
lastWriteAt = clientConn.LastWriteAt()
|
||||||
|
|
||||||
|
var lastErr = clientConn.LastErr()
|
||||||
|
if lastErr != nil {
|
||||||
|
lastErrString = lastErr.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var age int64 = -1
|
||||||
|
var lastReadAge int64 = -1
|
||||||
|
var lastWriteAge int64 = -1
|
||||||
|
var currentTime = time.Now().Unix()
|
||||||
|
if createdAt > 0 {
|
||||||
|
age = currentTime - createdAt
|
||||||
|
}
|
||||||
|
if lastReadAt > 0 {
|
||||||
|
lastReadAge = currentTime - lastReadAt
|
||||||
|
}
|
||||||
|
if lastWriteAt > 0 {
|
||||||
|
lastWriteAge = currentTime - lastWriteAt
|
||||||
|
}
|
||||||
|
|
||||||
|
connMaps = append(connMaps, maps.Map{
|
||||||
|
"addr": conn.RemoteAddr().String(),
|
||||||
|
"age": age,
|
||||||
|
"readAge": lastReadAge,
|
||||||
|
"writeAge": lastWriteAge,
|
||||||
|
"lastErr": lastErrString,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
sort.Slice(connMaps, func(i, j int) bool {
|
||||||
|
var m1 = connMaps[i]
|
||||||
|
var m2 = connMaps[j]
|
||||||
|
return m1.GetInt64("age") < m2.GetInt64("age")
|
||||||
|
})
|
||||||
|
|
||||||
_ = cmd.Reply(&gosock.Command{
|
_ = cmd.Reply(&gosock.Command{
|
||||||
Params: map[string]interface{}{
|
Params: map[string]interface{}{
|
||||||
"addrs": addrs,
|
"conns": connMaps,
|
||||||
"total": len(addrs),
|
"total": len(connMaps),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
case "dropIP":
|
case "dropIP":
|
||||||
@@ -839,6 +961,11 @@ func (this *Node) listenSock() error {
|
|||||||
} else {
|
} else {
|
||||||
_ = cmd.ReplyOk()
|
_ = cmd.ReplyOk()
|
||||||
}
|
}
|
||||||
|
case "closeIP":
|
||||||
|
var m = maps.NewMap(cmd.Params)
|
||||||
|
var ip = m.GetString("ip")
|
||||||
|
conns.SharedMap.CloseIPConns(ip)
|
||||||
|
_ = cmd.ReplyOk()
|
||||||
case "removeIP":
|
case "removeIP":
|
||||||
var m = maps.NewMap(cmd.Params)
|
var m = maps.NewMap(cmd.Params)
|
||||||
var ip = m.GetString("ip")
|
var ip = m.GetString("ip")
|
||||||
@@ -889,12 +1016,12 @@ func (this *Node) listenSock() error {
|
|||||||
|
|
||||||
err := this.sock.Listen()
|
err := this.sock.Listen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("NODE", err.Error())
|
remotelogs.Debug("NODE", err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
events.OnKey(events.EventQuit, this, func() {
|
events.OnKey(events.EventQuit, this, func() {
|
||||||
remotelogs.Println("NODE", "quit unix sock")
|
remotelogs.Debug("NODE", "quit unix sock")
|
||||||
_ = this.sock.Close()
|
_ = this.sock.Close()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -902,97 +1029,165 @@ func (this *Node) listenSock() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重载配置调用
|
// 重载配置调用
|
||||||
func (this *Node) onReload(config *nodeconfigs.NodeConfig) {
|
func (this *Node) onReload(config *nodeconfigs.NodeConfig, reloadAll bool) {
|
||||||
nodeconfigs.ResetNodeConfig(config)
|
nodeconfigs.ResetNodeConfig(config)
|
||||||
sharedNodeConfig = config
|
sharedNodeConfig = config
|
||||||
|
|
||||||
// 缓存策略
|
if reloadAll {
|
||||||
caches.SharedManager.MaxDiskCapacity = config.MaxCacheDiskCapacity
|
// 缓存策略
|
||||||
caches.SharedManager.MaxMemoryCapacity = config.MaxCacheMemoryCapacity
|
var subDirs = config.CacheDiskSubDirs
|
||||||
caches.SharedManager.DiskDir = config.CacheDiskDir
|
for _, subDir := range subDirs {
|
||||||
if len(config.HTTPCachePolicies) > 0 {
|
subDir.Path = filepath.Clean(subDir.Path)
|
||||||
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies)
|
}
|
||||||
} else {
|
if len(subDirs) > 0 {
|
||||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
sort.Slice(subDirs, func(i, j int) bool {
|
||||||
|
return subDirs[i].Path < subDirs[j].Path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var cachePoliciesChanged = !jsonutils.Equal(caches.SharedManager.MaxDiskCapacity, config.MaxCacheDiskCapacity) ||
|
||||||
|
!jsonutils.Equal(caches.SharedManager.MaxMemoryCapacity, config.MaxCacheMemoryCapacity) ||
|
||||||
|
!jsonutils.Equal(caches.SharedManager.MainDiskDir, config.CacheDiskDir) ||
|
||||||
|
!jsonutils.Equal(caches.SharedManager.SubDiskDirs, subDirs) ||
|
||||||
|
!jsonutils.Equal(this.oldHTTPCachePolicies, config.HTTPCachePolicies)
|
||||||
|
|
||||||
|
caches.SharedManager.MaxDiskCapacity = config.MaxCacheDiskCapacity
|
||||||
|
caches.SharedManager.MaxMemoryCapacity = config.MaxCacheMemoryCapacity
|
||||||
|
caches.SharedManager.MainDiskDir = config.CacheDiskDir
|
||||||
|
caches.SharedManager.SubDiskDirs = subDirs
|
||||||
|
|
||||||
|
if cachePoliciesChanged {
|
||||||
|
// copy
|
||||||
|
this.oldHTTPCachePolicies = []*serverconfigs.HTTPCachePolicy{}
|
||||||
|
err := jsonutils.Copy(&this.oldHTTPCachePolicies, config.HTTPCachePolicies)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "onReload: copy HTTPCachePolicies failed: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
if len(config.HTTPCachePolicies) > 0 {
|
||||||
|
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies)
|
||||||
|
} else {
|
||||||
|
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WAF策略
|
// WAF策略
|
||||||
waf.SharedWAFManager.UpdatePolicies(config.FindAllFirewallPolicies())
|
// 包含了服务里的WAF策略,所以需要整体更新
|
||||||
iplibrary.SharedActionManager.UpdateActions(config.FirewallActions)
|
var allFirewallPolicies = config.FindAllFirewallPolicies()
|
||||||
|
if !jsonutils.Equal(allFirewallPolicies, this.oldHTTPFirewallPolicies) {
|
||||||
// 统计指标
|
// copy
|
||||||
metrics.SharedManager.Update(config.MetricItems)
|
this.oldHTTPFirewallPolicies = []*firewallconfigs.HTTPFirewallPolicy{}
|
||||||
|
err := jsonutils.Copy(&this.oldHTTPFirewallPolicies, allFirewallPolicies)
|
||||||
// max cpu
|
|
||||||
if config.MaxCPU != this.maxCPU {
|
|
||||||
if config.MaxCPU > 0 && config.MaxCPU < int32(runtime.NumCPU()) {
|
|
||||||
runtime.GOMAXPROCS(int(config.MaxCPU))
|
|
||||||
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(config.MaxCPU)+"'")
|
|
||||||
} else {
|
|
||||||
var threads = runtime.NumCPU() * 4
|
|
||||||
runtime.GOMAXPROCS(threads)
|
|
||||||
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(threads)+"'")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.maxCPU = config.MaxCPU
|
|
||||||
}
|
|
||||||
|
|
||||||
// max threads
|
|
||||||
if config.MaxThreads != this.maxThreads {
|
|
||||||
if config.MaxThreads > 0 {
|
|
||||||
debug.SetMaxThreads(config.MaxThreads)
|
|
||||||
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(config.MaxThreads)+"'")
|
|
||||||
} else {
|
|
||||||
debug.SetMaxThreads(nodeconfigs.DefaultMaxThreads)
|
|
||||||
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(nodeconfigs.DefaultMaxThreads)+"'")
|
|
||||||
}
|
|
||||||
this.maxThreads = config.MaxThreads
|
|
||||||
}
|
|
||||||
|
|
||||||
// timezone
|
|
||||||
var timeZone = config.TimeZone
|
|
||||||
if len(timeZone) == 0 {
|
|
||||||
timeZone = "Asia/Shanghai"
|
|
||||||
}
|
|
||||||
|
|
||||||
if this.timezone != timeZone {
|
|
||||||
location, err := time.LoadLocation(timeZone)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("NODE", "[TIMEZONE]change time zone failed: "+err.Error())
|
remotelogs.Error("NODE", "onReload: copy HTTPFirewallPolicies failed: "+err.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remotelogs.Println("NODE", "[TIMEZONE]change time zone to '"+timeZone+"'")
|
// update
|
||||||
time.Local = location
|
waf.SharedWAFManager.UpdatePolicies(allFirewallPolicies)
|
||||||
this.timezone = timeZone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// product information
|
if reloadAll {
|
||||||
if config.ProductConfig != nil {
|
if !jsonutils.Equal(config.FirewallActions, this.oldFirewallActions) {
|
||||||
teaconst.GlobalProductName = config.ProductConfig.Name
|
// copy
|
||||||
}
|
this.oldFirewallActions = []*firewallconfigs.FirewallActionConfig{}
|
||||||
|
err := jsonutils.Copy(&this.oldFirewallActions, config.FirewallActions)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "onReload: copy FirewallActionConfigs failed: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// DNS resolver
|
// update
|
||||||
if config.DNSResolver != nil {
|
iplibrary.SharedActionManager.UpdateActions(config.FirewallActions)
|
||||||
var err error
|
}
|
||||||
switch config.DNSResolver.Type {
|
|
||||||
case nodeconfigs.DNSResolverTypeGoNative:
|
// 统计指标
|
||||||
err = os.Setenv("GODEBUG", "netdns=go")
|
if !jsonutils.Equal(this.oldMetricItems, config.MetricItems) {
|
||||||
case nodeconfigs.DNSResolverTypeCGO:
|
// copy
|
||||||
err = os.Setenv("GODEBUG", "netdns=cgo")
|
this.oldMetricItems = []*serverconfigs.MetricItemConfig{}
|
||||||
default:
|
err := jsonutils.Copy(&this.oldMetricItems, config.MetricItems)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "onReload: copy MetricItemConfigs failed: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
metrics.SharedManager.Update(config.MetricItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
// max cpu
|
||||||
|
if config.MaxCPU != this.oldMaxCPU {
|
||||||
|
if config.MaxCPU > 0 && config.MaxCPU < int32(runtime.NumCPU()) {
|
||||||
|
runtime.GOMAXPROCS(int(config.MaxCPU))
|
||||||
|
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(config.MaxCPU)+"'")
|
||||||
|
} else {
|
||||||
|
var threads = runtime.NumCPU() * 4
|
||||||
|
runtime.GOMAXPROCS(threads)
|
||||||
|
remotelogs.Println("NODE", "[CPU]set max cpu to '"+types.String(threads)+"'")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldMaxCPU = config.MaxCPU
|
||||||
|
}
|
||||||
|
|
||||||
|
// max threads
|
||||||
|
if config.MaxThreads != this.oldMaxThreads {
|
||||||
|
if config.MaxThreads > 0 {
|
||||||
|
debug.SetMaxThreads(config.MaxThreads)
|
||||||
|
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(config.MaxThreads)+"'")
|
||||||
|
} else {
|
||||||
|
debug.SetMaxThreads(nodeconfigs.DefaultMaxThreads)
|
||||||
|
remotelogs.Println("NODE", "[THREADS]set max threads to '"+types.String(nodeconfigs.DefaultMaxThreads)+"'")
|
||||||
|
}
|
||||||
|
this.oldMaxThreads = config.MaxThreads
|
||||||
|
}
|
||||||
|
|
||||||
|
// timezone
|
||||||
|
var timeZone = config.TimeZone
|
||||||
|
if len(timeZone) == 0 {
|
||||||
|
timeZone = "Asia/Shanghai"
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.oldTimezone != timeZone {
|
||||||
|
location, err := time.LoadLocation(timeZone)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "[TIMEZONE]change time zone failed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remotelogs.Println("NODE", "[TIMEZONE]change time zone to '"+timeZone+"'")
|
||||||
|
time.Local = location
|
||||||
|
this.oldTimezone = timeZone
|
||||||
|
}
|
||||||
|
|
||||||
|
// product information
|
||||||
|
if config.ProductConfig != nil {
|
||||||
|
teaconst.GlobalProductName = config.ProductConfig.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS resolver
|
||||||
|
if config.DNSResolver != nil {
|
||||||
|
var err error
|
||||||
|
switch config.DNSResolver.Type {
|
||||||
|
case nodeconfigs.DNSResolverTypeGoNative:
|
||||||
|
err = os.Setenv("GODEBUG", "netdns=go")
|
||||||
|
case nodeconfigs.DNSResolverTypeCGO:
|
||||||
|
err = os.Setenv("GODEBUG", "netdns=cgo")
|
||||||
|
default:
|
||||||
|
// 默认使用go原生
|
||||||
|
err = os.Setenv("GODEBUG", "netdns=go")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// 默认使用go原生
|
// 默认使用go原生
|
||||||
err = os.Setenv("GODEBUG", "netdns=go")
|
err := os.Setenv("GODEBUG", "netdns=go")
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
|
||||||
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 默认使用go原生
|
|
||||||
err := os.Setenv("GODEBUG", "netdns=go")
|
|
||||||
if err != nil {
|
|
||||||
remotelogs.Error("NODE", "[DNS_RESOLVER]set env failed: "+err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API Node地址,这里不限制是否为空,因为在为空时仍然要有对应的处理
|
||||||
|
this.changeAPINodeAddrs(config.APINodeAddrs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,7 +1223,7 @@ func (this *Node) reloadServer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onReload(newNodeConfig)
|
this.onReload(newNodeConfig, false)
|
||||||
|
|
||||||
err = sharedListenerManager.Start(newNodeConfig)
|
err = sharedListenerManager.Start(newNodeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1037,6 +1232,7 @@ func (this *Node) reloadServer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查硬盘
|
||||||
func (this *Node) checkDisk() {
|
func (this *Node) checkDisk() {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
return
|
return
|
||||||
@@ -1057,3 +1253,69 @@ func (this *Node) checkDisk() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查API节点地址
|
||||||
|
func (this *Node) changeAPINodeAddrs(apiNodeAddrs []*serverconfigs.NetworkAddressConfig) {
|
||||||
|
var addrs = []string{}
|
||||||
|
for _, addr := range apiNodeAddrs {
|
||||||
|
err := addr.Init()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "changeAPINodeAddrs: validate api node address '"+configutils.QuoteIP(addr.Host)+":"+addr.PortRange+"' failed: "+err.Error())
|
||||||
|
} else {
|
||||||
|
addrs = append(addrs, addr.FullAddresses()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(addrs)
|
||||||
|
|
||||||
|
if utils.EqualStrings(this.lastAPINodeAddrs, addrs) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastAPINodeAddrs = addrs
|
||||||
|
|
||||||
|
config, err := configs.LoadAPIConfig()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "changeAPINodeAddrs: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var oldEndpoints = config.RPC.Endpoints
|
||||||
|
|
||||||
|
rpcClient, err := rpc.SharedRPC()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(addrs) > 0 {
|
||||||
|
this.lastAPINodeVersion++
|
||||||
|
var v = this.lastAPINodeVersion
|
||||||
|
|
||||||
|
// 异步检测,防止阻塞
|
||||||
|
go func(v int64) {
|
||||||
|
// 测试新的API节点地址
|
||||||
|
if rpcClient.TestEndpoints(addrs) {
|
||||||
|
config.RPC.Endpoints = addrs
|
||||||
|
} else {
|
||||||
|
config.RPC.Endpoints = oldEndpoints
|
||||||
|
this.lastAPINodeAddrs = nil // 恢复为空,以便于下次更新重试
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查测试中间有无新的变更
|
||||||
|
if v != this.lastAPINodeVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rpcClient.UpdateConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "changeAPINodeAddrs: update rpc config failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rpcClient.UpdateConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("NODE", "changeAPINodeAddrs: update rpc config failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,12 +31,15 @@ type NodeStatusExecutor struct {
|
|||||||
cpuLogicalCount int
|
cpuLogicalCount int
|
||||||
cpuPhysicalCount int
|
cpuPhysicalCount int
|
||||||
|
|
||||||
|
apiCallStat *rpc.CallStat
|
||||||
|
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNodeStatusExecutor() *NodeStatusExecutor {
|
func NewNodeStatusExecutor() *NodeStatusExecutor {
|
||||||
return &NodeStatusExecutor{
|
return &NodeStatusExecutor{
|
||||||
ticker: time.NewTicker(30 * time.Second),
|
ticker: time.NewTicker(30 * time.Second),
|
||||||
|
apiCallStat: rpc.NewCallStat(10),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +81,11 @@ func (this *NodeStatusExecutor) update() {
|
|||||||
status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize()
|
status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize()
|
||||||
status.TrafficInBytes = teaconst.InTrafficBytes
|
status.TrafficInBytes = teaconst.InTrafficBytes
|
||||||
status.TrafficOutBytes = teaconst.OutTrafficBytes
|
status.TrafficOutBytes = teaconst.OutTrafficBytes
|
||||||
|
|
||||||
|
apiSuccessPercent, apiAvgCostSeconds := this.apiCallStat.Sum()
|
||||||
|
status.APISuccessPercent = apiSuccessPercent
|
||||||
|
status.APIAvgCostSeconds = apiAvgCostSeconds
|
||||||
|
|
||||||
var localFirewall = firewalls.Firewall()
|
var localFirewall = firewalls.Firewall()
|
||||||
if localFirewall != nil && !localFirewall.IsMock() {
|
if localFirewall != nil && !localFirewall.IsMock() {
|
||||||
status.LocalFirewallName = localFirewall.Name()
|
status.LocalFirewallName = localFirewall.Name()
|
||||||
@@ -125,9 +133,13 @@ func (this *NodeStatusExecutor) update() {
|
|||||||
remotelogs.Error("NODE_STATUS", "failed to open rpc: "+err.Error())
|
remotelogs.Error("NODE_STATUS", "failed to open rpc: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var before = time.Now()
|
||||||
_, err = rpcClient.NodeRPC.UpdateNodeStatus(rpcClient.Context(), &pb.UpdateNodeStatusRequest{
|
_, err = rpcClient.NodeRPC.UpdateNodeStatus(rpcClient.Context(), &pb.UpdateNodeStatusRequest{
|
||||||
StatusJSON: jsonData,
|
StatusJSON: jsonData,
|
||||||
})
|
})
|
||||||
|
var costSeconds = time.Since(before).Seconds()
|
||||||
|
this.apiCallStat.Add(err == nil, costSeconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rpc.IsConnError(err) {
|
if rpc.IsConnError(err) {
|
||||||
remotelogs.Warn("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
|
remotelogs.Warn("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
|
||||||
@@ -140,7 +152,7 @@ func (this *NodeStatusExecutor) update() {
|
|||||||
|
|
||||||
// 更新CPU
|
// 更新CPU
|
||||||
func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
|
func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
|
||||||
duration := time.Duration(0)
|
var duration = time.Duration(0)
|
||||||
if this.isFirstTime {
|
if this.isFirstTime {
|
||||||
duration = 100 * time.Millisecond
|
duration = 100 * time.Millisecond
|
||||||
}
|
}
|
||||||
@@ -195,8 +207,8 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 当前TeaWeb所在的fs
|
// 当前TeaWeb所在的fs
|
||||||
rootFS := ""
|
var rootFS = ""
|
||||||
rootTotal := uint64(0)
|
var rootTotal = uint64(0)
|
||||||
if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
|
if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
|
||||||
for _, p := range partitions {
|
for _, p := range partitions {
|
||||||
if p.Mountpoint == "/" {
|
if p.Mountpoint == "/" {
|
||||||
@@ -210,9 +222,9 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
total := rootTotal
|
var total = rootTotal
|
||||||
totalUsage := uint64(0)
|
var totalUsage = uint64(0)
|
||||||
maxUsage := float64(0)
|
var maxUsage = float64(0)
|
||||||
for _, partition := range partitions {
|
for _, partition := range partitions {
|
||||||
if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") {
|
if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") {
|
||||||
continue
|
continue
|
||||||
@@ -252,7 +264,7 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
|||||||
// 缓存空间
|
// 缓存空间
|
||||||
func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus) {
|
func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus) {
|
||||||
var result = []maps.Map{}
|
var result = []maps.Map{}
|
||||||
cachePaths := caches.SharedManager.FindAllCachePaths()
|
var cachePaths = caches.SharedManager.FindAllCachePaths()
|
||||||
for _, path := range cachePaths {
|
for _, path := range cachePaths {
|
||||||
var stat unix.Statfs_t
|
var stat unix.Statfs_t
|
||||||
err := unix.Statfs(path, &stat)
|
err := unix.Statfs(path, &stat)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !windows
|
//go:build !windows
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
|
||||||
"github.com/iwind/TeaGo/types"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sharedCNAMEManager = NewServerCNAMEManager()
|
|
||||||
|
|
||||||
// ServerCNAMEManager 服务CNAME管理
|
|
||||||
// TODO 需要自动更新缓存里的记录
|
|
||||||
type ServerCNAMEManager struct {
|
|
||||||
ttlCache *ttlcache.Cache
|
|
||||||
|
|
||||||
locker sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServerCNAMEManager() *ServerCNAMEManager {
|
|
||||||
return &ServerCNAMEManager{
|
|
||||||
ttlCache: ttlcache.NewCache(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *ServerCNAMEManager) Lookup(domain string) string {
|
|
||||||
if len(domain) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = this.ttlCache.Read(domain)
|
|
||||||
if item != nil {
|
|
||||||
return types.String(item.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
cname, _ := utils.LookupCNAME(domain)
|
|
||||||
if len(cname) > 0 {
|
|
||||||
cname = strings.TrimSuffix(cname, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ttlCache.Write(domain, cname, time.Now().Unix()+600)
|
|
||||||
|
|
||||||
return cname
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package nodes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServerCNameManager_Lookup(t *testing.T) {
|
|
||||||
var cnameManager = NewServerCNAMEManager()
|
|
||||||
t.Log(cnameManager.Lookup("www.yun4s.cn"))
|
|
||||||
|
|
||||||
var before = time.Now()
|
|
||||||
defer func() {
|
|
||||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
|
||||||
}()
|
|
||||||
t.Log(cnameManager.Lookup("www.yun4s.cn"))
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -20,15 +21,18 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
var manager = NewSystemServiceManager()
|
var manager = NewSystemServiceManager()
|
||||||
events.On(events.EventReload, func() {
|
events.On(events.EventReload, func() {
|
||||||
err := manager.Setup()
|
goman.New(func() {
|
||||||
if err != nil {
|
err := manager.Setup()
|
||||||
remotelogs.Error("SYSTEM_SERVICE", "setup system services failed: "+err.Error())
|
if err != nil {
|
||||||
}
|
remotelogs.Error("SYSTEM_SERVICE", "setup system services failed: "+err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemServiceManager 系统服务管理
|
// SystemServiceManager 系统服务管理
|
||||||
type SystemServiceManager struct {
|
type SystemServiceManager struct {
|
||||||
|
lastIsOn int // -1, 0, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemServiceManager() *SystemServiceManager {
|
func NewSystemServiceManager() *SystemServiceManager {
|
||||||
@@ -68,7 +72,8 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config := &nodeconfigs.SystemdServiceConfig{}
|
|
||||||
|
var config = &nodeconfigs.SystemdServiceConfig{}
|
||||||
err = json.Unmarshal(data, config)
|
err = json.Unmarshal(data, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -82,42 +87,60 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
|
|||||||
if len(systemctl) == 0 {
|
if len(systemctl) == 0 {
|
||||||
return errors.New("can not find 'systemctl' on the system")
|
return errors.New("can not find 'systemctl' on the system")
|
||||||
}
|
}
|
||||||
cmd := utils.NewCommandExecutor()
|
|
||||||
shortName := teaconst.SystemdServiceName
|
// 记录上次状态
|
||||||
cmd.Add(systemctl, "is-enabled", shortName)
|
var isOnInt int
|
||||||
output, err := cmd.Run()
|
if config.IsOn {
|
||||||
if err != nil {
|
isOnInt = 1
|
||||||
return err
|
} else {
|
||||||
|
isOnInt = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if this.lastIsOn == isOnInt {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
this.lastIsOn = isOnInt
|
||||||
|
}()
|
||||||
|
|
||||||
|
var shortName = teaconst.SystemdServiceName
|
||||||
|
var cmd = executils.NewTimeoutCmd(10*time.Second, systemctl, "is-enabled", shortName)
|
||||||
|
cmd.WithStdout()
|
||||||
|
err = cmd.Run()
|
||||||
|
var hasInstalled = err == nil
|
||||||
if config.IsOn {
|
if config.IsOn {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动Service
|
// 检查文件路径是否变化
|
||||||
goman.New(func() {
|
if hasInstalled && cmd.Stdout() == "enabled" {
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
_ = exec.Command(systemctl, "start", teaconst.SystemdServiceName).Start()
|
|
||||||
})
|
|
||||||
|
|
||||||
if output == "enabled" {
|
|
||||||
// 检查文件路径是否变化
|
|
||||||
data, err := os.ReadFile("/etc/systemd/system/" + teaconst.SystemdServiceName + ".service")
|
data, err := os.ReadFile("/etc/systemd/system/" + teaconst.SystemdServiceName + ".service")
|
||||||
if err == nil && bytes.Index(data, []byte(exe)) > 0 {
|
if err == nil && bytes.Index(data, []byte(exe)) > 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
|
|
||||||
|
// 安装服务
|
||||||
|
var manager = utils.NewServiceManager(shortName, teaconst.ProductName)
|
||||||
err = manager.Install(exe, []string{})
|
err = manager.Install(exe, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 启动服务
|
||||||
|
goman.New(func() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
_ = executils.NewTimeoutCmd(30*time.Second, systemctl, "start", teaconst.SystemdServiceName).Start()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
|
if hasInstalled {
|
||||||
err = manager.Uninstall()
|
var manager = utils.NewServiceManager(shortName, teaconst.ProductName)
|
||||||
if err != nil {
|
err = manager.Uninstall()
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user