Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7763f26249 | ||
|
|
b7ae10e2d0 | ||
|
|
e54eddc961 | ||
|
|
93db9d4926 | ||
|
|
8c1af3e699 | ||
|
|
53c74553bc | ||
|
|
3eb9cade0e | ||
|
|
eeee3da941 | ||
|
|
6af59e0bd0 | ||
|
|
012233baf2 | ||
|
|
ac069fd7f3 | ||
|
|
40cb1916c2 | ||
|
|
749e0bd0b3 | ||
|
|
ae1a9abf5e | ||
|
|
7fc0394f10 | ||
|
|
4a169a2dbd | ||
|
|
44d8afeda8 | ||
|
|
6a0547abec | ||
|
|
6d002e2822 | ||
|
|
04271d77c2 | ||
|
|
5a6ead1dd7 | ||
|
|
9ac7b9b2c0 | ||
|
|
7ec916c1fb | ||
|
|
fadc580dff | ||
|
|
fb9e9fb94b | ||
|
|
9c6e4bb8c1 | ||
|
|
97b04777bc | ||
|
|
4daeca912a | ||
|
|
7e43324b53 | ||
|
|
b9b8472c3a | ||
|
|
6858380bb4 | ||
|
|
568ecadfc6 | ||
|
|
8210ece2b7 | ||
|
|
f8160e35b9 | ||
|
|
9e4a1212d2 | ||
|
|
6f52cffabd | ||
|
|
c546b9fc7d | ||
|
|
f7b961d256 | ||
|
|
71cbb2d695 | ||
|
|
2063015eeb | ||
|
|
87cc43b2e0 | ||
|
|
9812883b61 | ||
|
|
068c20e1b9 | ||
|
|
17d883a2de | ||
|
|
083bbb1460 | ||
|
|
4f7b9f4fc6 | ||
|
|
b679fc3b25 | ||
|
|
ced82d8a30 | ||
|
|
73108faa3e | ||
|
|
e89460e36f | ||
|
|
cd19a4a7bc | ||
|
|
4dcd47d8bc | ||
|
|
388e7d2683 | ||
|
|
36f9effbf2 | ||
|
|
8bb47fb915 | ||
|
|
7127d26fff | ||
|
|
8fae094161 | ||
|
|
16349041fb | ||
|
|
2793e0de89 | ||
|
|
82a7971718 | ||
|
|
1ef59ccf65 | ||
|
|
cc5d2f1862 | ||
|
|
5bff24b88d | ||
|
|
bc2f8b1e4c | ||
|
|
8935f35b4e | ||
|
|
874694f662 | ||
|
|
27506ae436 | ||
|
|
3d9f40331d | ||
|
|
538f18afb0 | ||
|
|
b4d9fc02bd | ||
|
|
537138c8a1 | ||
|
|
f0d0a39a03 | ||
|
|
bcce2a2767 | ||
|
|
86db3dfc49 | ||
|
|
ac120a728c | ||
|
|
871de0e655 |
@@ -1,4 +1,4 @@
|
||||
rpc:
|
||||
endpoints: [ ${endpoints} ]
|
||||
nodeId: "${nodeId}"
|
||||
secret: "${nodeSecret}"
|
||||
endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -60,6 +61,33 @@ func main() {
|
||||
node := nodes.NewNode()
|
||||
node.Start()
|
||||
})
|
||||
app.On("trackers", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
reply, err := sock.Send(&gosock.Command{Code: "trackers"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
} else {
|
||||
labelsMap, ok := reply.Params["labels"]
|
||||
if ok {
|
||||
labels, ok := labelsMap.(map[string]interface{})
|
||||
if ok {
|
||||
if len(labels) == 0 {
|
||||
fmt.Println("no labels yet")
|
||||
} else {
|
||||
var labelNames = []string{}
|
||||
for label := range labels {
|
||||
labelNames = append(labelNames, label)
|
||||
}
|
||||
sort.Strings(labelNames)
|
||||
|
||||
for _, labelName := range labelNames {
|
||||
fmt.Println(labelName + ": " + fmt.Sprintf("%.6f", labels[labelName]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
app.Run(func() {
|
||||
node := nodes.NewNode()
|
||||
node.Start()
|
||||
|
||||
13
go.mod
13
go.mod
@@ -8,19 +8,26 @@ require (
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/andybalholm/brotli v1.0.3
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/chai2010/webp v1.1.0
|
||||
github.com/chai2010/webp v1.1.0 // indirect
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060
|
||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
||||
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/mssola/user_agent v0.5.2
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible
|
||||
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
|
||||
|
||||
21
go.sum
21
go.sum
@@ -10,6 +10,8 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrU
|
||||
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
|
||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
@@ -76,11 +78,19 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060 h1:qdLtK4PDXxk2vMKkTWl5Fl9xqYuRCukzWAgJbLHdfOo=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24 h1:1cGulkD2SNJJRok5OKwyhP/Ddm+PgSWKOupn0cR36/A=
|
||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8 h1:AojsHz9Es9B3He2MQQxeRq3TyD//o9huxUo7r1wh44g=
|
||||
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8/go.mod h1:QJBY2txYhLMzwLO29iB5ujDJ3s3V7DsZ582nw4Ss+tM=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -90,10 +100,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible h1:1qp9iks+69h7IGLazAplzS9Ca14HAxuD5c0rbFdPGy4=
|
||||
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo=
|
||||
github.com/mssola/user_agent v0.5.2/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
@@ -106,6 +124,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -157,6 +177,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -184,6 +184,8 @@ func (this *AppCmd) runStart() {
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.Setenv("EdgeBackground", "on")
|
||||
|
||||
cmd := exec.Command(os.Args[0])
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
|
||||
@@ -6,6 +6,10 @@ import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/utils/time"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
@@ -34,7 +38,26 @@ func (this *LogWriter) Init() {
|
||||
}
|
||||
|
||||
func (this *LogWriter) Write(message string) {
|
||||
log.Println(message)
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
// 文件和行号
|
||||
var file string
|
||||
var line int
|
||||
if Tea.IsTesting() {
|
||||
var callDepth = 3
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(callDepth)
|
||||
if ok {
|
||||
file = this.packagePath(file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(file) > 0 {
|
||||
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
|
||||
} else {
|
||||
log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
if this.fileAppender != nil {
|
||||
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
@@ -49,3 +72,11 @@ func (this *LogWriter) Close() {
|
||||
_ = this.fileAppender.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *LogWriter) packagePath(path string) string {
|
||||
var pieces = strings.Split(path, "/")
|
||||
if len(pieces) >= 2 {
|
||||
return strings.Join(pieces[len(pieces)-2:], "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
10
internal/caches/hot_item.go
Normal file
10
internal/caches/hot_item.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
|
||||
type HotItem struct {
|
||||
Key string
|
||||
ExpiresAt int64
|
||||
Hits uint32
|
||||
Status int
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemType = int
|
||||
@@ -11,6 +12,12 @@ const (
|
||||
ItemTypeMemory ItemType = 2
|
||||
)
|
||||
|
||||
// 计算当前周
|
||||
// 不要用YW,因为需要计算两周是否临近
|
||||
func currentWeek() int32 {
|
||||
return int32(time.Now().Unix() / 86400)
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Type ItemType `json:"type"`
|
||||
Key string `json:"key"`
|
||||
@@ -20,6 +27,10 @@ type Item struct {
|
||||
MetaSize int64 `json:"metaSize"`
|
||||
Host string `json:"host"` // 主机名
|
||||
ServerId int64 `json:"serverId"` // 服务ID
|
||||
|
||||
Week1Hits int64 `json:"week1Hits"`
|
||||
Week2Hits int64 `json:"week2Hits"`
|
||||
Week int32 `json:"week"`
|
||||
}
|
||||
|
||||
func (this *Item) IsExpired() bool {
|
||||
@@ -27,9 +38,23 @@ func (this *Item) IsExpired() bool {
|
||||
}
|
||||
|
||||
func (this *Item) TotalSize() int64 {
|
||||
return this.Size() + this.MetaSize + int64(len(this.Key)) + 64
|
||||
return this.Size() + this.MetaSize + int64(len(this.Key)) + int64(len(this.Host))
|
||||
}
|
||||
|
||||
func (this *Item) Size() int64 {
|
||||
return this.HeaderSize + this.BodySize
|
||||
}
|
||||
|
||||
func (this *Item) IncreaseHit(week int32) {
|
||||
if this.Week == week {
|
||||
this.Week2Hits++
|
||||
} else {
|
||||
if week-this.Week == 1 {
|
||||
this.Week1Hits = this.Week2Hits
|
||||
} else {
|
||||
this.Week1Hits = 0
|
||||
}
|
||||
this.Week2Hits = 1
|
||||
this.Week = week
|
||||
}
|
||||
}
|
||||
|
||||
82
internal/caches/item_test.go
Normal file
82
internal/caches/item_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestItem_IncreaseHit(t *testing.T) {
|
||||
var week = currentWeek()
|
||||
|
||||
var item = &Item{}
|
||||
//item.Week = 2704
|
||||
item.Week2Hits = 100
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
}
|
||||
|
||||
func TestItems_Memory(t *testing.T) {
|
||||
var stat = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = []*Item{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
items = append(items, &Item{
|
||||
Key: types.String(i),
|
||||
})
|
||||
}
|
||||
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory2 = stat.HeapInuse
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
var weekItems = make(map[string]*Item, 10_000_000)
|
||||
|
||||
for _, item := range items {
|
||||
weekItems[item.Key] = item
|
||||
}
|
||||
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory3 = stat.HeapInuse
|
||||
t.Log(memory2, memory3, (memory3-memory2)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(len(items), len(weekItems))
|
||||
}
|
||||
|
||||
func TestItems_Memory2(t *testing.T) {
|
||||
var stat = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = map[int32]map[string]bool{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
var week = int32((time.Now().Unix() - int64(86400*rands.Int(0, 300))) / (86400 * 7))
|
||||
m, ok := items[week]
|
||||
if !ok {
|
||||
m = map[string]bool{}
|
||||
items[week] = m
|
||||
}
|
||||
m[types.String(int64(i)*1_000_000)] = true
|
||||
}
|
||||
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory2 = stat.HeapInuse
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
for w, i := range items {
|
||||
t.Log(w, len(i))
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -24,6 +25,7 @@ type FileList struct {
|
||||
onAdd func(item *Item)
|
||||
onRemove func(item *Item)
|
||||
|
||||
// cacheItems
|
||||
existsByHashStmt *sql.Stmt // 根据hash检查是否存在
|
||||
insertStmt *sql.Stmt // 写入数据
|
||||
selectByHashStmt *sql.Stmt // 使用hash查询数据
|
||||
@@ -32,8 +34,15 @@ type FileList struct {
|
||||
purgeStmt *sql.Stmt // 清理
|
||||
deleteAllStmt *sql.Stmt // 删除所有数据
|
||||
|
||||
// hits
|
||||
insertHitStmt *sql.Stmt // 写入数据
|
||||
increaseHitStmt *sql.Stmt // 增加点击量
|
||||
deleteHitByHashStmt *sql.Stmt // 根据hash删除数据
|
||||
lfuHitsStmt *sql.Stmt // 读取老的数据
|
||||
|
||||
oldTables []string
|
||||
itemsTableName string
|
||||
hitsTableName string
|
||||
|
||||
isClosed bool
|
||||
|
||||
@@ -59,6 +68,7 @@ func (this *FileList) Init() error {
|
||||
}
|
||||
|
||||
this.itemsTableName = "cacheItems_v2"
|
||||
this.hitsTableName = "hits"
|
||||
|
||||
var dir = this.dir
|
||||
if dir == "/" {
|
||||
@@ -141,6 +151,23 @@ func (this *FileList) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertHitStmt, err = this.db.Prepare(`INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`)
|
||||
|
||||
this.increaseHitStmt, err = this.db.Prepare(`INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?) ON CONFLICT("hash") DO UPDATE SET "week1Hits"=IIF("week"=?, "week1Hits", "week2Hits"), "week2Hits"=IIF("week"=?, "week2Hits"+1, 1), "week"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.deleteHitByHashStmt, err = this.db.Prepare(`DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.lfuHitsStmt, err = this.db.Prepare(`SELECT "hash" FROM "` + this.hitsTableName + `" ORDER BY "week" ASC, "week1Hits"+"week2Hits" ASC LIMIT ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -159,6 +186,11 @@ func (this *FileList) Add(hash string, item *Item) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.insertHitStmt.Exec(hash, timeutil.Format("YW"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.total, 1)
|
||||
|
||||
if this.onAdd != nil {
|
||||
@@ -253,6 +285,11 @@ func (this *FileList) Remove(hash string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.deleteHitByHashStmt.Exec(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.total, -1)
|
||||
|
||||
if this.onRemove != nil {
|
||||
@@ -265,9 +302,9 @@ func (this *FileList) Remove(hash string) error {
|
||||
// Purge 清理过期的缓存
|
||||
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
|
||||
// callback 每次发现过期key的调用
|
||||
func (this *FileList) Purge(count int, callback func(hash string) error) error {
|
||||
func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if count <= 0 {
|
||||
@@ -275,11 +312,56 @@ func (this *FileList) Purge(count int, callback func(hash string) error) error {
|
||||
}
|
||||
|
||||
rows, err := this.purgeStmt.Query(time.Now().Unix(), count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
hashStrings := []string{}
|
||||
var countFound = 0
|
||||
for rows.Next() {
|
||||
var hash string
|
||||
err = rows.Scan(&hash)
|
||||
if err != nil {
|
||||
_ = rows.Close()
|
||||
return 0, err
|
||||
}
|
||||
hashStrings = append(hashStrings, hash)
|
||||
countFound++
|
||||
}
|
||||
_ = rows.Close() // 不能使用defer,防止读写冲突
|
||||
|
||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||
for _, hash := range hashStrings {
|
||||
err = this.Remove(hash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = callback(hash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return countFound, nil
|
||||
}
|
||||
|
||||
func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if count <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rows, err := this.lfuHitsStmt.Query(count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashStrings := []string{}
|
||||
var countFound = 0
|
||||
for rows.Next() {
|
||||
var hash string
|
||||
err = rows.Scan(&hash)
|
||||
@@ -288,6 +370,7 @@ func (this *FileList) Purge(count int, callback func(hash string) error) error {
|
||||
return err
|
||||
}
|
||||
hashStrings = append(hashStrings, hash)
|
||||
countFound++
|
||||
}
|
||||
_ = rows.Close() // 不能使用defer,防止读写冲突
|
||||
|
||||
@@ -303,7 +386,6 @@ func (this *FileList) Purge(count int, callback func(hash string) error) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -347,6 +429,13 @@ func (this *FileList) Count() (int64, error) {
|
||||
return atomic.LoadInt64(&this.total), nil
|
||||
}
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
func (this *FileList) IncreaseHit(hash string) error {
|
||||
var week = timeutil.Format("YW")
|
||||
_, err := this.increaseHitStmt.Exec(hash, week, week, week, week)
|
||||
return err
|
||||
}
|
||||
|
||||
// OnAdd 添加事件
|
||||
func (this *FileList) OnAdd(f func(item *Item)) {
|
||||
this.onAdd = f
|
||||
@@ -371,6 +460,11 @@ func (this *FileList) Close() error {
|
||||
_ = this.purgeStmt.Close()
|
||||
_ = this.deleteAllStmt.Close()
|
||||
|
||||
_ = this.insertHitStmt.Close()
|
||||
_ = this.increaseHitStmt.Close()
|
||||
_ = this.deleteHitByHashStmt.Close()
|
||||
_ = this.lfuHitsStmt.Close()
|
||||
|
||||
return this.db.Close()
|
||||
}
|
||||
return nil
|
||||
@@ -378,7 +472,8 @@ func (this *FileList) Close() error {
|
||||
|
||||
// 初始化
|
||||
func (this *FileList) initTables(db *sql.DB, times int) error {
|
||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
||||
{
|
||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"key" varchar(1024),
|
||||
@@ -411,17 +506,46 @@ ON "` + this.itemsTableName + `" (
|
||||
"serverId" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
// 尝试删除重建
|
||||
if times < 3 {
|
||||
_, dropErr := db.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
||||
if dropErr == nil {
|
||||
return this.initTables(db, times+1)
|
||||
if err != nil {
|
||||
// 尝试删除重建
|
||||
if times < 3 {
|
||||
_, dropErr := db.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
||||
if dropErr == nil {
|
||||
return this.initTables(db, times+1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
{
|
||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"week1Hits" integer DEFAULT 0,
|
||||
"week2Hits" integer DEFAULT 0,
|
||||
"week" varchar(6)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
|
||||
ON "` + this.hitsTableName + `" (
|
||||
"hash" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
// 尝试删除重建
|
||||
if times < 3 {
|
||||
_, dropErr := db.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
|
||||
if dropErr == nil {
|
||||
return this.initTables(db, times+1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ package caches
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -184,7 +185,7 @@ func TestFileList_Purge(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = list.Purge(2, func(hash string) error {
|
||||
_, err = list.Purge(2, func(hash string) error {
|
||||
t.Log(hash)
|
||||
return nil
|
||||
})
|
||||
@@ -257,6 +258,50 @@ func TestFileList_Conflict(t *testing.T) {
|
||||
t.Log("after exists")
|
||||
}
|
||||
|
||||
func TestFileList_IIF(t *testing.T) {
|
||||
list := NewFileList(Tea.Root + "/data").(*FileList)
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := list.db.Query("SELECT IIF(0, 2, 3)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
if rows.Next() {
|
||||
var result int
|
||||
err = rows.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("result:", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileList_IncreaseHit(t *testing.T) {
|
||||
list := NewFileList(Tea.Root + "/data")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
for i := 0; i < 1000_000; i++ {
|
||||
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func BenchmarkFileList_Exist(b *testing.B) {
|
||||
list := NewFileList(Tea.Root + "/data")
|
||||
err := list.Init()
|
||||
|
||||
@@ -22,7 +22,10 @@ type ListInterface interface {
|
||||
Remove(hash string) error
|
||||
|
||||
// Purge 清理过期数据
|
||||
Purge(count int, callback func(hash string) error) error
|
||||
Purge(count int, callback func(hash string) error) (int, error)
|
||||
|
||||
// PurgeLFU 清理LFU数据
|
||||
PurgeLFU(count int, callback func(hash string) error) error
|
||||
|
||||
// CleanAll 清除所有缓存
|
||||
CleanAll() error
|
||||
@@ -41,4 +44,7 @@ type ListInterface interface {
|
||||
|
||||
// Close 关闭
|
||||
Close() error
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
IncreaseHit(hash string) error
|
||||
}
|
||||
|
||||
@@ -5,12 +5,19 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MemoryList 内存缓存列表管理
|
||||
type MemoryList struct {
|
||||
count int64
|
||||
|
||||
itemMaps map[string]map[string]*Item // prefix => { hash => item }
|
||||
|
||||
weekItemMaps map[int32]map[string]bool // week => { hash => true }
|
||||
minWeek int32
|
||||
|
||||
prefixes []string
|
||||
locker sync.RWMutex
|
||||
onAdd func(item *Item)
|
||||
@@ -21,7 +28,9 @@ type MemoryList struct {
|
||||
|
||||
func NewMemoryList() ListInterface {
|
||||
return &MemoryList{
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
weekItemMaps: map[int32]map[string]bool{},
|
||||
minWeek: currentWeek(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +52,19 @@ func (this *MemoryList) Reset() error {
|
||||
for key := range this.itemMaps {
|
||||
this.itemMaps[key] = map[string]*Item{}
|
||||
}
|
||||
this.weekItemMaps = map[int32]map[string]bool{}
|
||||
this.locker.Unlock()
|
||||
|
||||
atomic.StoreInt64(&this.count, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
if item.Week == 0 {
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
prefix := this.prefix(hash)
|
||||
@@ -60,9 +77,20 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
// 先删除,为了可以正确触发统计
|
||||
oldItem, ok := itemMap[hash]
|
||||
if ok {
|
||||
// 从week map中删除
|
||||
if oldItem.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[oldItem.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// 回调
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(oldItem)
|
||||
}
|
||||
} else {
|
||||
atomic.AddInt64(&this.count, 1)
|
||||
}
|
||||
|
||||
// 添加
|
||||
@@ -71,6 +99,15 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
}
|
||||
|
||||
itemMap[hash] = item
|
||||
|
||||
// week map
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
wm[hash] = true
|
||||
} else {
|
||||
this.weekItemMaps[item.Week] = map[string]bool{hash: true}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -122,7 +159,17 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -132,7 +179,7 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
// Purge 清理过期的缓存
|
||||
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
|
||||
// callback 每次发现过期key的调用
|
||||
func (this *MemoryList) Purge(count int, callback func(hash string) error) error {
|
||||
func (this *MemoryList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
this.locker.Lock()
|
||||
deletedHashList := []string{}
|
||||
|
||||
@@ -146,8 +193,9 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) error
|
||||
itemMap, ok := this.itemMaps[prefix]
|
||||
if !ok {
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
return 0, nil
|
||||
}
|
||||
var countFound = 0
|
||||
for hash, item := range itemMap {
|
||||
if count <= 0 {
|
||||
break
|
||||
@@ -157,14 +205,100 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) error
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
countFound++
|
||||
}
|
||||
|
||||
count--
|
||||
}
|
||||
this.locker.Unlock()
|
||||
|
||||
// 执行外部操作
|
||||
for _, hash := range deletedHashList {
|
||||
if callback != nil {
|
||||
err := callback(hash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return countFound, nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) PurgeLFU(count int, callback func(hash string) error) error {
|
||||
if count <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var week = currentWeek()
|
||||
if this.minWeek > week {
|
||||
this.minWeek = week
|
||||
}
|
||||
|
||||
var deletedHashList = []string{}
|
||||
|
||||
Loop:
|
||||
for w := this.minWeek; w <= week; w++ {
|
||||
this.minWeek = w
|
||||
|
||||
this.locker.Lock()
|
||||
wm, ok := this.weekItemMaps[w]
|
||||
if ok {
|
||||
var wc = len(wm)
|
||||
if wc == 0 {
|
||||
delete(this.weekItemMaps, w)
|
||||
} else {
|
||||
if wc <= count {
|
||||
delete(this.weekItemMaps, w)
|
||||
}
|
||||
|
||||
// TODO 未来支持按照点击量排序
|
||||
for hash := range wm {
|
||||
count--
|
||||
|
||||
if count < 0 {
|
||||
this.locker.Unlock()
|
||||
break Loop
|
||||
}
|
||||
|
||||
delete(wm, hash)
|
||||
|
||||
itemMap, ok := this.itemMaps[this.prefix(hash)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item, ok := itemMap[hash]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete(this.weekItemMaps, w)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// 执行外部操作
|
||||
for _, hash := range deletedHashList {
|
||||
if callback != nil {
|
||||
@@ -174,6 +308,7 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -206,13 +341,8 @@ func (this *MemoryList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
|
||||
// Count 总数量
|
||||
func (this *MemoryList) Count() (int64, error) {
|
||||
this.locker.RLock()
|
||||
var count = 0
|
||||
for _, itemMap := range this.itemMaps {
|
||||
count += len(itemMap)
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
return int64(count), nil
|
||||
var count = atomic.LoadInt64(&this.count)
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// OnAdd 添加事件
|
||||
@@ -229,6 +359,41 @@ func (this *MemoryList) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
func (this *MemoryList) IncreaseHit(hash string) error {
|
||||
this.locker.Lock()
|
||||
|
||||
itemMap, ok := this.itemMaps[this.prefix(hash)]
|
||||
if !ok {
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
item, ok := itemMap[hash]
|
||||
if ok {
|
||||
var week = currentWeek()
|
||||
|
||||
// 交换位置
|
||||
if item.Week > 0 && item.Week != week {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
wm, ok = this.weekItemMaps[week]
|
||||
if ok {
|
||||
wm[hash] = true
|
||||
} else {
|
||||
this.weekItemMaps[week] = map[string]bool{hash: true}
|
||||
}
|
||||
}
|
||||
|
||||
item.IncreaseHit(week)
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) print(t *testing.T) {
|
||||
this.locker.Lock()
|
||||
for _, itemMap := range this.itemMaps {
|
||||
|
||||
@@ -31,6 +31,8 @@ func TestMemoryList_Add(t *testing.T) {
|
||||
})
|
||||
t.Log(list.prefixes)
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Remove(t *testing.T) {
|
||||
@@ -48,6 +50,8 @@ func TestMemoryList_Remove(t *testing.T) {
|
||||
})
|
||||
_ = list.Remove("b")
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Purge(t *testing.T) {
|
||||
@@ -73,19 +77,22 @@ func TestMemoryList_Purge(t *testing.T) {
|
||||
ExpiredAt: time.Now().Unix() - 2,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Purge(100, func(hash string) error {
|
||||
_, _ = list.Purge(100, func(hash string) error {
|
||||
t.Log("delete:", hash)
|
||||
return nil
|
||||
})
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
_ = list.Purge(100, func(hash string) error {
|
||||
_, _ = list.Purge(100, func(hash string) error {
|
||||
t.Log("delete:", hash)
|
||||
return nil
|
||||
})
|
||||
t.Log(list.purgeIndex)
|
||||
}
|
||||
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Purge_Large_List(t *testing.T) {
|
||||
@@ -139,7 +146,7 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
_ = list.Init()
|
||||
before := time.Now()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
key := "http://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html"
|
||||
key := "https://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html"
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
|
||||
Key: key,
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
@@ -150,7 +157,7 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
|
||||
before = time.Now()
|
||||
err := list.CleanPrefix("http://www.teaos.cn/hello/10")
|
||||
err := list.CleanPrefix("https://www.teaos.cn/hello/10")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -162,11 +169,77 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
list.minWeek = 2704
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
t.Log("current week:", currentWeek())
|
||||
|
||||
_ = list.Add("1", &Item{})
|
||||
_ = list.Add("2", &Item{})
|
||||
_ = list.Add("3", &Item{})
|
||||
_ = list.Add("4", &Item{})
|
||||
_ = list.Add("5", &Item{})
|
||||
_ = list.Add("6", &Item{Week: 2704})
|
||||
_ = list.Add("7", &Item{Week: 2704})
|
||||
_ = list.Add("8", &Item{Week: 2705})
|
||||
|
||||
err := list.PurgeLFU(2, func(hash string) error {
|
||||
t.Log("purge lfu:", hash)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_IncreaseHit(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
_ = list.Add("a", item)
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
}
|
||||
|
||||
func TestMemoryList_CleanAll(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
_ = list.CleanAll()
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_GC(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
_ = list.Init()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
key := "http://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html"
|
||||
key := "https://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html"
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
|
||||
Key: key,
|
||||
ExpiredAt: 0,
|
||||
|
||||
@@ -135,7 +135,7 @@ func (this *Manager) NewStorageWithPolicy(policy *serverconfigs.HTTPCachePolicy)
|
||||
case serverconfigs.CachePolicyStorageFile:
|
||||
return NewFileStorage(policy)
|
||||
case serverconfigs.CachePolicyStorageMemory:
|
||||
return NewMemoryStorage(policy)
|
||||
return NewMemoryStorage(policy, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ type Reader interface {
|
||||
// TypeName 类型名称
|
||||
TypeName() string
|
||||
|
||||
// ExpiresAt 过期时间
|
||||
ExpiresAt() int64
|
||||
|
||||
// Status 状态码
|
||||
Status() int
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type FileReader struct {
|
||||
fp *os.File
|
||||
|
||||
expiresAt int64
|
||||
status int
|
||||
headerOffset int64
|
||||
headerSize int
|
||||
@@ -43,6 +44,8 @@ func (this *FileReader) Init() error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
|
||||
|
||||
status := types.Int(string(buf[SizeExpiresAt : SizeExpiresAt+SizeStatus]))
|
||||
if status < 100 || status > 999 {
|
||||
return errors.New("invalid status")
|
||||
@@ -78,6 +81,10 @@ func (this *FileReader) TypeName() string {
|
||||
return "disk"
|
||||
}
|
||||
|
||||
func (this *FileReader) ExpiresAt() int64 {
|
||||
return this.expiresAt
|
||||
}
|
||||
|
||||
func (this *FileReader) Status() int {
|
||||
return this.status
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ func (this *MemoryReader) TypeName() string {
|
||||
return "memory"
|
||||
}
|
||||
|
||||
func (this *MemoryReader) ExpiresAt() int64 {
|
||||
return this.item.ExpiredAt
|
||||
}
|
||||
|
||||
func (this *MemoryReader) Status() int {
|
||||
return this.item.Status
|
||||
}
|
||||
|
||||
@@ -9,15 +9,20 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -36,6 +41,10 @@ const (
|
||||
SizeMeta = SizeExpiresAt + SizeStatus + SizeURLLength + SizeHeaderLength + SizeBodyLength
|
||||
)
|
||||
|
||||
const (
|
||||
HotItemSize = 1024
|
||||
)
|
||||
|
||||
// FileStorage 文件缓存
|
||||
// 文件结构:
|
||||
// [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data]
|
||||
@@ -48,13 +57,20 @@ type FileStorage struct {
|
||||
list ListInterface
|
||||
writingKeyMap map[string]bool // key => bool
|
||||
locker sync.RWMutex
|
||||
ticker *utils.Ticker
|
||||
purgeTicker *utils.Ticker
|
||||
|
||||
hotMap map[string]*HotItem // key => count
|
||||
hotMapLocker sync.Mutex
|
||||
lastHotSize int
|
||||
hotTicker *utils.Ticker
|
||||
}
|
||||
|
||||
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
||||
return &FileStorage{
|
||||
policy: policy,
|
||||
writingKeyMap: map[string]bool{},
|
||||
hotMap: map[string]*HotItem{},
|
||||
lastHotSize: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,12 +181,16 @@ func (this *FileStorage) Init() error {
|
||||
Life: this.policy.Life,
|
||||
MinLife: this.policy.MinLife,
|
||||
MaxLife: this.policy.MaxLife,
|
||||
|
||||
MemoryAutoPurgeCount: this.policy.MemoryAutoPurgeCount,
|
||||
MemoryAutoPurgeInterval: this.policy.MemoryAutoPurgeInterval,
|
||||
MemoryLFUFreePercent: this.policy.MemoryLFUFreePercent,
|
||||
}
|
||||
err = memoryPolicy.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memoryStorage := NewMemoryStorage(memoryPolicy)
|
||||
memoryStorage := NewMemoryStorage(memoryPolicy, this)
|
||||
err = memoryStorage.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,8 +203,12 @@ func (this *FileStorage) Init() error {
|
||||
}
|
||||
|
||||
func (this *FileStorage) OpenReader(key string) (Reader, error) {
|
||||
return this.openReader(key, true)
|
||||
}
|
||||
|
||||
func (this *FileStorage) openReader(key string, allowMemory bool) (Reader, error) {
|
||||
// 先尝试内存缓存
|
||||
if this.memoryStorage != nil {
|
||||
if allowMemory && this.memoryStorage != nil {
|
||||
reader, err := this.memoryStorage.OpenReader(key)
|
||||
if err == nil {
|
||||
return reader, err
|
||||
@@ -227,6 +251,45 @@ func (this *FileStorage) OpenReader(key string) (Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 增加点击量
|
||||
// 1/1000采样
|
||||
if allowMemory {
|
||||
var rate = this.policy.PersistenceHitSampleRate
|
||||
if rate <= 0 {
|
||||
rate = 1000
|
||||
}
|
||||
if this.lastHotSize == 0 {
|
||||
// 自动降低采样率来增加热点数据的缓存几率
|
||||
rate = rate / 10
|
||||
}
|
||||
if rands.Int(0, rate) == 0 {
|
||||
var hitErr = this.list.IncreaseHit(hash)
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
|
||||
// 增加到热点
|
||||
// 这里不收录缓存尺寸过大的文件
|
||||
if this.memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*1024*1024 {
|
||||
this.hotMapLocker.Lock()
|
||||
hotItem, ok := this.hotMap[key]
|
||||
if ok {
|
||||
hotItem.Hits++
|
||||
hotItem.ExpiresAt = reader.expiresAt
|
||||
} else if len(this.hotMap) < HotItemSize { // 控制数量
|
||||
this.hotMap[key] = &HotItem{
|
||||
Key: key,
|
||||
ExpiresAt: reader.ExpiresAt(),
|
||||
Status: reader.Status(),
|
||||
Hits: 1,
|
||||
}
|
||||
}
|
||||
this.hotMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isOk = true
|
||||
return reader, nil
|
||||
}
|
||||
@@ -398,7 +461,7 @@ func (this *FileStorage) AddToList(item *Item) {
|
||||
}
|
||||
}
|
||||
|
||||
item.MetaSize = SizeMeta
|
||||
item.MetaSize = SizeMeta + 128
|
||||
hash := stringutil.Md5(item.Key)
|
||||
err := this.list.Add(hash, item)
|
||||
if err != nil && !strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||
@@ -555,8 +618,11 @@ func (this *FileStorage) Stop() {
|
||||
}
|
||||
|
||||
_ = this.list.Reset()
|
||||
if this.ticker != nil {
|
||||
this.ticker.Stop()
|
||||
if this.purgeTicker != nil {
|
||||
this.purgeTicker.Stop()
|
||||
}
|
||||
if this.hotTicker != nil {
|
||||
this.hotTicker.Stop()
|
||||
}
|
||||
|
||||
_ = this.list.Close()
|
||||
@@ -606,30 +672,47 @@ func (this *FileStorage) initList() error {
|
||||
}
|
||||
|
||||
// 使用异步防止阻塞主线程
|
||||
go func() {
|
||||
/**go func() {
|
||||
dir := this.dir()
|
||||
|
||||
// 清除tmp
|
||||
files, err := filepath.Glob(dir + "/*/*/*.cache.tmp")
|
||||
if err == nil && len(files) > 0 {
|
||||
for _, path := range files {
|
||||
_ = os.Remove(path)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// TODO 需要一个更加高效的实现
|
||||
}()**/
|
||||
|
||||
// 启动定时清理任务
|
||||
this.ticker = utils.NewTicker(30 * time.Second)
|
||||
var autoPurgeInterval = this.policy.PersistenceAutoPurgeInterval
|
||||
if autoPurgeInterval <= 0 {
|
||||
autoPurgeInterval = 30
|
||||
if Tea.IsTesting() {
|
||||
autoPurgeInterval = 10
|
||||
}
|
||||
}
|
||||
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
|
||||
events.On(events.EventQuit, func() {
|
||||
remotelogs.Println("CACHE", "quit clean timer")
|
||||
var ticker = this.ticker
|
||||
var ticker = this.purgeTicker
|
||||
if ticker != nil {
|
||||
ticker.Stop()
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
for this.ticker.Next() {
|
||||
this.purgeLoop()
|
||||
for this.purgeTicker.Next() {
|
||||
trackers.Run("FILE_CACHE_STORAGE_PURGE_LOOP", func() {
|
||||
this.purgeLoop()
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 热点处理任务
|
||||
this.hotTicker = utils.NewTicker(1 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
this.hotTicker = utils.NewTicker(10 * time.Second)
|
||||
}
|
||||
go func() {
|
||||
for this.hotTicker.Next() {
|
||||
trackers.Run("FILE_CACHE_STORAGE_HOT_LOOP", func() {
|
||||
this.hotLoop()
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -730,16 +813,183 @@ func (this *FileStorage) decodeFile(path string) (*Item, error) {
|
||||
|
||||
// 清理任务
|
||||
func (this *FileStorage) purgeLoop() {
|
||||
err := this.list.Purge(1000, func(hash string) error {
|
||||
path := this.hashPath(hash)
|
||||
err := os.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.policy.CapacityBytes()
|
||||
var startLFU = false
|
||||
var usedPercent = float32(this.TotalDiskSize()*100) / float32(capacityBytes)
|
||||
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
if capacityBytes > 0 {
|
||||
if lfuFreePercent < 100 {
|
||||
if usedPercent >= 100-lfuFreePercent {
|
||||
startLFU = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理过期
|
||||
{
|
||||
var times = 1
|
||||
|
||||
// 空闲时间多清理
|
||||
if utils.SharedFreeHoursManager.IsFreeHour() {
|
||||
times = 5
|
||||
}
|
||||
|
||||
// 处于LFU阈值时,多清理
|
||||
if startLFU {
|
||||
times = 5
|
||||
}
|
||||
|
||||
var purgeCount = this.policy.PersistenceAutoPurgeCount
|
||||
if purgeCount <= 0 {
|
||||
purgeCount = 1000
|
||||
}
|
||||
for i := 0; i < times; i++ {
|
||||
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
||||
path := this.hashPath(hash)
|
||||
err := os.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge file storage failed: "+err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if countFound < purgeCount {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// 磁盘空间不足时,清除老旧的缓存
|
||||
if startLFU {
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count > 0 {
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > 2000 {
|
||||
count = 2000
|
||||
}
|
||||
|
||||
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 {
|
||||
path := this.hashPath(hash)
|
||||
err := os.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge file storage in LFU failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 热点数据任务
|
||||
func (this *FileStorage) hotLoop() {
|
||||
var memoryStorage = this.memoryStorage
|
||||
if memoryStorage == nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.hotMapLocker.Lock()
|
||||
if len(this.hotMap) == 0 {
|
||||
this.hotMapLocker.Unlock()
|
||||
this.lastHotSize = 0
|
||||
return
|
||||
}
|
||||
|
||||
this.lastHotSize = len(this.hotMap)
|
||||
|
||||
var result = []*HotItem{} // [ {key: ..., hits: ...}, ... ]
|
||||
for _, v := range this.hotMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
this.hotMap = map[string]*HotItem{}
|
||||
this.hotMapLocker.Unlock()
|
||||
|
||||
// 取Top10
|
||||
if len(result) > 0 {
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Hits > result[j].Hits
|
||||
})
|
||||
var size = 1
|
||||
if len(result) < 10 {
|
||||
size = 1
|
||||
} else {
|
||||
size = len(result) / 10
|
||||
}
|
||||
|
||||
var buf = make([]byte, 32*1024)
|
||||
for _, item := range result[:size] {
|
||||
reader, err := this.openReader(item.Key, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if reader == nil {
|
||||
continue
|
||||
}
|
||||
if reader.ExpiresAt() <= time.Now().Unix() {
|
||||
continue
|
||||
}
|
||||
|
||||
writer, err := this.memoryStorage.openWriter(item.Key, item.ExpiresAt, item.Status, false)
|
||||
if err != nil {
|
||||
if !CanIgnoreErr(err) {
|
||||
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
||||
}
|
||||
_ = reader.Close()
|
||||
continue
|
||||
}
|
||||
if writer == nil {
|
||||
_ = reader.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) {
|
||||
_, err = writer.WriteHeader(buf[:n])
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
_ = writer.Discard()
|
||||
continue
|
||||
}
|
||||
|
||||
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
||||
_, err = writer.Write(buf[:n])
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
_ = writer.Discard()
|
||||
continue
|
||||
}
|
||||
|
||||
this.memoryStorage.AddToList(&Item{
|
||||
Type: writer.ItemType(),
|
||||
Key: item.Key,
|
||||
ExpiredAt: item.ExpiresAt,
|
||||
HeaderSize: writer.HeaderSize(),
|
||||
BodySize: writer.BodySize(),
|
||||
})
|
||||
|
||||
_ = reader.Close()
|
||||
_ = writer.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge file storage failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -517,3 +517,16 @@ func BenchmarkFileStorage_Read(b *testing.B) {
|
||||
_ = reader.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFileStorage_KeyPath(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var storage = &FileStorage{
|
||||
cacheConfig: &serverconfigs.HTTPFileCacheStorage{},
|
||||
policy: &serverconfigs.HTTPCachePolicy{Id: 1},
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = storage.keyPath(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,13 @@ import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -26,22 +31,37 @@ func (this *MemoryItem) IsExpired() bool {
|
||||
}
|
||||
|
||||
type MemoryStorage struct {
|
||||
policy *serverconfigs.HTTPCachePolicy
|
||||
list ListInterface
|
||||
locker *sync.RWMutex
|
||||
valuesMap map[uint64]*MemoryItem
|
||||
ticker *utils.Ticker
|
||||
purgeDuration time.Duration
|
||||
parentStorage StorageInterface
|
||||
|
||||
policy *serverconfigs.HTTPCachePolicy
|
||||
list ListInterface
|
||||
locker *sync.RWMutex
|
||||
|
||||
valuesMap map[uint64]*MemoryItem // hash => item
|
||||
dirtyChan chan string // hash chan
|
||||
|
||||
purgeTicker *utils.Ticker
|
||||
|
||||
totalSize int64
|
||||
writingKeyMap map[string]bool // key => bool
|
||||
}
|
||||
|
||||
func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy) *MemoryStorage {
|
||||
func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage StorageInterface) *MemoryStorage {
|
||||
var dirtyChan chan string
|
||||
if parentStorage != nil {
|
||||
var queueSize = policy.MemoryAutoFlushQueueSize
|
||||
if queueSize <= 0 {
|
||||
queueSize = 2048
|
||||
}
|
||||
dirtyChan = make(chan string, queueSize)
|
||||
}
|
||||
return &MemoryStorage{
|
||||
parentStorage: parentStorage,
|
||||
policy: policy,
|
||||
list: NewMemoryList(),
|
||||
locker: &sync.RWMutex{},
|
||||
valuesMap: map[uint64]*MemoryItem{},
|
||||
dirtyChan: dirtyChan,
|
||||
writingKeyMap: map[string]bool{},
|
||||
}
|
||||
}
|
||||
@@ -57,15 +77,25 @@ func (this *MemoryStorage) Init() error {
|
||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
||||
})
|
||||
|
||||
if this.purgeDuration <= 0 {
|
||||
this.purgeDuration = 10 * time.Second
|
||||
var autoPurgeInterval = this.policy.MemoryAutoPurgeInterval
|
||||
if autoPurgeInterval <= 0 {
|
||||
autoPurgeInterval = 5
|
||||
}
|
||||
|
||||
// 启动定时清理任务
|
||||
this.ticker = utils.NewTicker(this.purgeDuration)
|
||||
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
|
||||
go func() {
|
||||
for this.ticker.Next() {
|
||||
for this.purgeTicker.Next() {
|
||||
var tr = trackers.Begin("MEMORY_CACHE_STORAGE_PURGE_LOOP")
|
||||
this.purgeLoop()
|
||||
tr.End()
|
||||
}
|
||||
}()
|
||||
|
||||
// 启动定时Flush memory to disk任务
|
||||
go func() {
|
||||
for hash := range this.dirtyChan {
|
||||
this.flushItem(hash)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -91,6 +121,18 @@ func (this *MemoryStorage) OpenReader(key string) (Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
// 增加点击量
|
||||
// 1/1000采样
|
||||
// TODO 考虑是否在缓存策略里设置
|
||||
if rands.Int(0, 1000) == 0 {
|
||||
var hitErr = this.list.IncreaseHit(types.String(hash))
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return reader, nil
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
@@ -102,6 +144,10 @@ func (this *MemoryStorage) OpenReader(key string) (Reader, error) {
|
||||
|
||||
// OpenWriter 打开缓存写入器等待写入
|
||||
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int) (Writer, error) {
|
||||
return this.openWriter(key, expiredAt, status, true)
|
||||
}
|
||||
|
||||
func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, isDirty bool) (Writer, error) {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
@@ -145,7 +191,7 @@ func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int) (
|
||||
}
|
||||
|
||||
isWriting = true
|
||||
return NewMemoryWriter(this.valuesMap, key, expiredAt, status, this.locker, func() {
|
||||
return NewMemoryWriter(this, key, expiredAt, status, isDirty, func() {
|
||||
this.locker.Lock()
|
||||
delete(this.writingKeyMap, key)
|
||||
this.locker.Unlock()
|
||||
@@ -210,14 +256,21 @@ func (this *MemoryStorage) Stop() {
|
||||
this.valuesMap = map[uint64]*MemoryItem{}
|
||||
this.writingKeyMap = map[string]bool{}
|
||||
_ = this.list.Reset()
|
||||
if this.ticker != nil {
|
||||
this.ticker.Stop()
|
||||
if this.purgeTicker != nil {
|
||||
this.purgeTicker.Stop()
|
||||
}
|
||||
|
||||
if this.parentStorage != nil && this.dirtyChan != nil {
|
||||
close(this.dirtyChan)
|
||||
}
|
||||
|
||||
_ = this.list.Close()
|
||||
|
||||
this.locker.Unlock()
|
||||
|
||||
// 回收内存
|
||||
runtime.GC()
|
||||
|
||||
remotelogs.Println("CACHE", "close memory storage '"+strconv.FormatInt(this.policy.Id, 10)+"'")
|
||||
}
|
||||
|
||||
@@ -228,7 +281,7 @@ func (this *MemoryStorage) Policy() *serverconfigs.HTTPCachePolicy {
|
||||
|
||||
// AddToList 将缓存添加到列表
|
||||
func (this *MemoryStorage) AddToList(item *Item) {
|
||||
item.MetaSize = int64(len(item.Key)) + 32 /** 32是我们评估的数据结构的长度 **/
|
||||
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
||||
hash := fmt.Sprintf("%d", this.hash(item.Key))
|
||||
_ = this.list.Add(hash, item)
|
||||
}
|
||||
@@ -250,7 +303,28 @@ func (this *MemoryStorage) hash(key string) uint64 {
|
||||
|
||||
// 清理任务
|
||||
func (this *MemoryStorage) purgeLoop() {
|
||||
_ = this.list.Purge(2048, func(hash string) error {
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.policy.CapacityBytes()
|
||||
var startLFU = false
|
||||
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
|
||||
var lfuFreePercent = this.policy.MemoryLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
if capacityBytes > 0 {
|
||||
if lfuFreePercent < 100 {
|
||||
if usedPercent >= 100-lfuFreePercent {
|
||||
startLFU = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理过期
|
||||
var purgeCount = this.policy.MemoryAutoPurgeCount
|
||||
if purgeCount <= 0 {
|
||||
purgeCount = 2000
|
||||
}
|
||||
_, _ = this.list.Purge(purgeCount, func(hash string) error {
|
||||
uintHash, err := strconv.ParseUint(hash, 10, 64)
|
||||
if err == nil {
|
||||
this.locker.Lock()
|
||||
@@ -259,6 +333,92 @@ func (this *MemoryStorage) purgeLoop() {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// LFU
|
||||
if startLFU {
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count > 0 {
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > 2000 {
|
||||
count = 2000
|
||||
}
|
||||
|
||||
// 这里不提示LFU,因为此事件将会非常频繁
|
||||
|
||||
err := this.list.PurgeLFU(count, func(hash string) error {
|
||||
uintHash, err := strconv.ParseUint(hash, 10, 64)
|
||||
if err == nil {
|
||||
this.locker.Lock()
|
||||
delete(this.valuesMap, uintHash)
|
||||
this.locker.Unlock()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge memory storage in LFU failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush任务
|
||||
func (this *MemoryStorage) flushItem(key string) {
|
||||
if this.parentStorage == nil {
|
||||
return
|
||||
}
|
||||
var hash = this.hash(key)
|
||||
|
||||
this.locker.RLock()
|
||||
item, ok := this.valuesMap[hash]
|
||||
this.locker.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !item.IsDone || item.IsExpired() {
|
||||
return
|
||||
}
|
||||
|
||||
writer, err := this.parentStorage.OpenWriter(key, item.ExpiredAt, item.Status)
|
||||
if err != nil {
|
||||
if !CanIgnoreErr(err) {
|
||||
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = writer.WriteHeader(item.HeaderValue)
|
||||
if err != nil {
|
||||
_ = writer.Discard()
|
||||
remotelogs.Error("CACHE", "flush items failed: write header failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err = writer.Write(item.BodyValue)
|
||||
if err != nil {
|
||||
_ = writer.Discard()
|
||||
remotelogs.Error("CACHE", "flush items failed: writer body failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
_ = writer.Discard()
|
||||
remotelogs.Error("CACHE", "flush items failed: close writer failed: "+err.Error())
|
||||
}
|
||||
|
||||
this.parentStorage.AddToList(&Item{
|
||||
Type: writer.ItemType(),
|
||||
Key: key,
|
||||
ExpiredAt: item.ExpiredAt,
|
||||
HeaderSize: writer.HeaderSize(),
|
||||
BodySize: writer.BodySize(),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
|
||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200)
|
||||
if err != nil {
|
||||
@@ -85,7 +88,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_OpenReaderLock(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
_ = storage.Init()
|
||||
|
||||
var h = storage.hash("test")
|
||||
@@ -98,7 +101,7 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Delete(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
{
|
||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200)
|
||||
if err != nil {
|
||||
@@ -120,7 +123,7 @@ func TestMemoryStorage_Delete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Stat(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
expiredAt := time.Now().Unix() + 60
|
||||
{
|
||||
writer, err := storage.OpenWriter("abc", expiredAt, 200)
|
||||
@@ -157,7 +160,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_CleanAll(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
expiredAt := time.Now().Unix() + 60
|
||||
{
|
||||
writer, err := storage.OpenWriter("abc", expiredAt, 200)
|
||||
@@ -192,7 +195,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Purge(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
expiredAt := time.Now().Unix() + 60
|
||||
{
|
||||
writer, err := storage.OpenWriter("abc", expiredAt, 200)
|
||||
@@ -227,8 +230,9 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Expire(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage.purgeDuration = 5 * time.Second
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||
MemoryAutoPurgeInterval: 5,
|
||||
}, nil)
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -252,7 +256,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Locker(t *testing.T) {
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{})
|
||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -265,3 +269,38 @@ func TestMemoryStorage_Locker(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Stop(t *testing.T) {
|
||||
var stat1 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat1)
|
||||
|
||||
var m = map[uint64]*MemoryItem{}
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
m[uint64(i)] = &MemoryItem{
|
||||
HeaderValue: []byte("Hello, World"),
|
||||
BodyValue: bytes.Repeat([]byte("Hello"), 1024),
|
||||
}
|
||||
}
|
||||
|
||||
m = map[uint64]*MemoryItem{}
|
||||
|
||||
var before = time.Now()
|
||||
//runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
/**go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
runtime.GC()
|
||||
}()**/
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
|
||||
var stat2 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat2)
|
||||
|
||||
if stat2.HeapInuse > stat1.HeapInuse {
|
||||
t.Log(stat2.HeapInuse, stat1.HeapInuse, (stat2.HeapInuse-stat1.HeapInuse)/1024/1024, "MB")
|
||||
} else {
|
||||
t.Log("0 MB")
|
||||
}
|
||||
|
||||
t.Log(len(m))
|
||||
}
|
||||
|
||||
@@ -2,36 +2,36 @@ package caches
|
||||
|
||||
import (
|
||||
"github.com/cespare/xxhash"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemoryWriter struct {
|
||||
storage *MemoryStorage
|
||||
|
||||
key string
|
||||
expiredAt int64
|
||||
m map[uint64]*MemoryItem
|
||||
locker *sync.RWMutex
|
||||
headerSize int64
|
||||
bodySize int64
|
||||
status int
|
||||
isDirty bool
|
||||
|
||||
hash uint64
|
||||
item *MemoryItem
|
||||
endFunc func()
|
||||
}
|
||||
|
||||
func NewMemoryWriter(m map[uint64]*MemoryItem, key string, expiredAt int64, status int, locker *sync.RWMutex, endFunc func()) *MemoryWriter {
|
||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, endFunc func()) *MemoryWriter {
|
||||
w := &MemoryWriter{
|
||||
m: m,
|
||||
storage: memoryStorage,
|
||||
key: key,
|
||||
expiredAt: expiredAt,
|
||||
locker: locker,
|
||||
item: &MemoryItem{
|
||||
ExpiredAt: expiredAt,
|
||||
ModifiedAt: time.Now().Unix(),
|
||||
Status: status,
|
||||
},
|
||||
status: status,
|
||||
isDirty: isDirty,
|
||||
endFunc: endFunc,
|
||||
}
|
||||
w.hash = w.calculateHash(key)
|
||||
@@ -72,10 +72,19 @@ func (this *MemoryWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
this.storage.locker.Lock()
|
||||
this.item.IsDone = true
|
||||
this.m[this.hash] = this.item
|
||||
this.locker.Unlock()
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
if this.isDirty {
|
||||
if this.storage.parentStorage != nil {
|
||||
select {
|
||||
case this.storage.dirtyChan <- this.key:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
this.storage.locker.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -85,9 +94,9 @@ func (this *MemoryWriter) Discard() error {
|
||||
// 需要在Locker之外
|
||||
defer this.endFunc()
|
||||
|
||||
this.locker.Lock()
|
||||
delete(this.m, this.hash)
|
||||
this.locker.Unlock()
|
||||
this.storage.locker.Lock()
|
||||
delete(this.storage.valuesMap, this.hash)
|
||||
this.storage.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
8
internal/compressions/reader.go
Normal file
8
internal/compressions/reader.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package compressions
|
||||
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
Close() error
|
||||
}
|
||||
24
internal/compressions/reader_brotli.go
Normal file
24
internal/compressions/reader_brotli.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"github.com/andybalholm/brotli"
|
||||
"io"
|
||||
)
|
||||
|
||||
type BrotliReader struct {
|
||||
reader *brotli.Reader
|
||||
}
|
||||
|
||||
func NewBrotliReader(reader io.Reader) (Reader, error) {
|
||||
return &BrotliReader{reader: brotli.NewReader(reader)}, nil
|
||||
}
|
||||
|
||||
func (this *BrotliReader) Read(p []byte) (n int, err error) {
|
||||
return this.reader.Read(p)
|
||||
}
|
||||
|
||||
func (this *BrotliReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
24
internal/compressions/reader_deflate.go
Normal file
24
internal/compressions/reader_deflate.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"io"
|
||||
)
|
||||
|
||||
type DeflateReader struct {
|
||||
reader io.ReadCloser
|
||||
}
|
||||
|
||||
func NewDeflateReader(reader io.Reader) (Reader, error) {
|
||||
return &DeflateReader{reader: flate.NewReader(reader)}, nil
|
||||
}
|
||||
|
||||
func (this *DeflateReader) Read(p []byte) (n int, err error) {
|
||||
return this.reader.Read(p)
|
||||
}
|
||||
|
||||
func (this *DeflateReader) Close() error {
|
||||
return this.reader.Close()
|
||||
}
|
||||
30
internal/compressions/reader_gzip.go
Normal file
30
internal/compressions/reader_gzip.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
)
|
||||
|
||||
type GzipReader struct {
|
||||
reader *gzip.Reader
|
||||
}
|
||||
|
||||
func NewGzipReader(reader io.Reader) (Reader, error) {
|
||||
r, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GzipReader{
|
||||
reader: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *GzipReader) Read(p []byte) (n int, err error) {
|
||||
return this.reader.Read(p)
|
||||
}
|
||||
|
||||
func (this *GzipReader) Close() error {
|
||||
return this.reader.Close()
|
||||
}
|
||||
@@ -8,6 +8,31 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ContentEncoding = string
|
||||
|
||||
const (
|
||||
ContentEncodingBr ContentEncoding = "br"
|
||||
ContentEncodingGzip ContentEncoding = "gzip"
|
||||
ContentEncodingDeflate ContentEncoding = "deflate"
|
||||
)
|
||||
|
||||
var ErrNotSupportedContentEncoding = errors.New("not supported content encoding")
|
||||
|
||||
// NewReader 获取Reader
|
||||
func NewReader(reader io.Reader, contentEncoding ContentEncoding) (Reader, error) {
|
||||
switch contentEncoding {
|
||||
case ContentEncodingBr:
|
||||
return NewBrotliReader(reader)
|
||||
case ContentEncodingGzip:
|
||||
return NewGzipReader(reader)
|
||||
case ContentEncodingDeflate:
|
||||
return NewDeflateReader(reader)
|
||||
}
|
||||
return nil, ErrNotSupportedContentEncoding
|
||||
}
|
||||
|
||||
// NewWriter 获取Writer
|
||||
// TODO 考虑重用Writer
|
||||
func NewWriter(writer io.Writer, compressType serverconfigs.HTTPCompressionType, level int) (Writer, error) {
|
||||
switch compressType {
|
||||
case serverconfigs.HTTPCompressionTypeGzip:
|
||||
|
||||
51
internal/compressions/writer_encoding.go
Normal file
51
internal/compressions/writer_encoding.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type EncodingWriter struct {
|
||||
contentEncoding ContentEncoding
|
||||
writer Writer
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewEncodingWriter(contentEncoding ContentEncoding, writer Writer) (Writer, error) {
|
||||
return &EncodingWriter{
|
||||
contentEncoding: contentEncoding,
|
||||
writer: writer,
|
||||
buf: &bytes.Buffer{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *EncodingWriter) Write(p []byte) (int, error) {
|
||||
return this.buf.Write(p)
|
||||
}
|
||||
|
||||
func (this *EncodingWriter) Flush() error {
|
||||
return this.writer.Flush()
|
||||
}
|
||||
|
||||
func (this *EncodingWriter) Close() error {
|
||||
reader, err := NewReader(this.buf, this.contentEncoding)
|
||||
if err != nil {
|
||||
_ = this.writer.Close()
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(this.writer, reader)
|
||||
if err != nil {
|
||||
_ = reader.Close()
|
||||
_ = this.writer.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
_ = reader.Close()
|
||||
return this.writer.Close()
|
||||
}
|
||||
|
||||
func (this *EncodingWriter) Level() int {
|
||||
return this.writer.Level()
|
||||
}
|
||||
47
internal/compressions/writer_encoding_test.go
Normal file
47
internal/compressions/writer_encoding_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewEncodingWriter(t *testing.T) {
|
||||
var buf = &bytes.Buffer{}
|
||||
|
||||
subWriter, err := NewWriter(buf, serverconfigs.HTTPCompressionTypeGzip, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writer, err := NewEncodingWriter(ContentEncodingGzip, subWriter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gzipBuf := &bytes.Buffer{}
|
||||
gzipWriter, err := NewGzipWriter(gzipBuf, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = gzipWriter.Write([]byte("Hello"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = gzipWriter.Write([]byte("World"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = gzipWriter.Close()
|
||||
|
||||
_, err = writer.Write(gzipBuf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = writer.Close()
|
||||
|
||||
t.Log(buf.String())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.3.2"
|
||||
Version = "0.3.6"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
|
||||
12
internal/const/vars.go
Normal file
12
internal/const/vars.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package teaconst
|
||||
|
||||
var (
|
||||
// 流量统计
|
||||
|
||||
InTrafficBytes = uint64(0)
|
||||
OutTrafficBytes = uint64(0)
|
||||
|
||||
NodeId int64 = 0
|
||||
)
|
||||
5
internal/iplibrary/README.md
Normal file
5
internal/iplibrary/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# IPList
|
||||
List Check Order:
|
||||
~~~
|
||||
Global List --> Node List--> Server List --> WAF List --> Bind List
|
||||
~~~
|
||||
@@ -13,7 +13,7 @@ func (this *BaseAction) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理HTTP请求
|
||||
// DoHTTP 处理HTTP请求
|
||||
func (this *BaseAction) DoHTTP(req *http.Request, resp http.ResponseWriter) (goNext bool, err error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package iplibrary
|
||||
|
||||
// 是否是致命错误
|
||||
// FataError 是否是致命错误
|
||||
type FataError struct {
|
||||
err string
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Firewalld动作管理
|
||||
// FirewalldAction Firewalld动作管理
|
||||
// 常用命令:
|
||||
// - 查询列表: firewall-cmd --list-all
|
||||
// - 添加IP:firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||
@@ -20,6 +20,8 @@ type FirewalldAction struct {
|
||||
BaseAction
|
||||
|
||||
config *firewallconfigs.FirewallActionFirewalldConfig
|
||||
|
||||
firewalldNotFound bool
|
||||
}
|
||||
|
||||
func NewFirewalldAction() *FirewalldAction {
|
||||
@@ -82,6 +84,10 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("firewall-cmd")
|
||||
if err != nil {
|
||||
if this.firewalldNotFound {
|
||||
return nil
|
||||
}
|
||||
this.firewalldNotFound = true
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -126,10 +132,12 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
}
|
||||
|
||||
args := []string{opt}
|
||||
if item.ExpiredAt > timestamp {
|
||||
args = append(args, "--timeout="+fmt.Sprintf("%d", item.ExpiredAt-timestamp)+"s")
|
||||
} else {
|
||||
// TODO 思考是否需要permanent,不然--reload之后会丢失
|
||||
if action == "addItem" {
|
||||
if item.ExpiredAt > timestamp {
|
||||
args = append(args, "--timeout="+fmt.Sprintf("%d", item.ExpiredAt-timestamp)+"s")
|
||||
} else {
|
||||
// TODO 思考是否需要permanent,不然--reload之后会丢失
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
|
||||
@@ -6,19 +6,19 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HTML动作
|
||||
// HTMLAction HTML动作
|
||||
type HTMLAction struct {
|
||||
BaseAction
|
||||
|
||||
config *firewallconfigs.FirewallActionHTMLConfig
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
// NewHTMLAction 获取新对象
|
||||
func NewHTMLAction() *HTMLAction {
|
||||
return &HTMLAction{}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
// Init 初始化
|
||||
func (this *HTMLAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||
this.config = &firewallconfigs.FirewallActionHTMLConfig{}
|
||||
err := this.convertParams(config.Params, this.config)
|
||||
@@ -28,22 +28,22 @@ func (this *HTMLAction) Init(config *firewallconfigs.FirewallActionConfig) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// 添加
|
||||
// AddItem 添加
|
||||
func (this *HTMLAction) AddItem(listType IPListType, item *pb.IPItem) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除
|
||||
// DeleteItem 删除
|
||||
func (this *HTMLAction) DeleteItem(listType IPListType, item *pb.IPItem) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 关闭
|
||||
// Close 关闭
|
||||
func (this *HTMLAction) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理HTTP请求
|
||||
// DoHTTP 处理HTTP请求
|
||||
func (this *HTMLAction) DoHTTP(req *http.Request, resp http.ResponseWriter) (goNext bool, err error) {
|
||||
if this.config == nil {
|
||||
goNext = true
|
||||
|
||||
@@ -7,18 +7,18 @@ import (
|
||||
)
|
||||
|
||||
type ActionInterface interface {
|
||||
// 初始化
|
||||
// Init 初始化
|
||||
Init(config *firewallconfigs.FirewallActionConfig) error
|
||||
|
||||
// 添加
|
||||
// AddItem 添加
|
||||
AddItem(listType IPListType, item *pb.IPItem) error
|
||||
|
||||
// 删除
|
||||
// DeleteItem 删除
|
||||
DeleteItem(listType IPListType, item *pb.IPItem) error
|
||||
|
||||
// 关闭
|
||||
// Close 关闭
|
||||
Close() error
|
||||
|
||||
// 处理HTTP请求
|
||||
// DoHTTP 处理HTTP请求
|
||||
DoHTTP(req *http.Request, resp http.ResponseWriter) (goNext bool, err error)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IPSet动作
|
||||
// IPSetAction IPSet动作
|
||||
// 相关命令:
|
||||
// - 利用Firewalld管理set:
|
||||
// - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
|
||||
@@ -23,14 +25,21 @@ import (
|
||||
// - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30
|
||||
// - 删除Item: ipset del edge_ip_list 192.168.2.32
|
||||
// - 创建set:ipset create edge_ip_list hash:ip timeout 0
|
||||
// - 查看统计:ipset -t list edge_black_list
|
||||
// - 删除set:ipset destroy edge_black_list
|
||||
type IPSetAction struct {
|
||||
BaseAction
|
||||
|
||||
config *firewallconfigs.FirewallActionIPSetConfig
|
||||
config *firewallconfigs.FirewallActionIPSetConfig
|
||||
errorBuf *bytes.Buffer
|
||||
|
||||
ipsetNotfound bool
|
||||
}
|
||||
|
||||
func NewIPSetAction() *IPSetAction {
|
||||
return &IPSetAction{}
|
||||
return &IPSetAction{
|
||||
errorBuf: &bytes.Buffer{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||
@@ -54,7 +63,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
return err
|
||||
}
|
||||
{
|
||||
cmd := exec.Command(path, "create", this.config.WhiteName, "hash:ip", "timeout", "0")
|
||||
cmd := exec.Command(path, "create", this.config.WhiteName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
||||
stderr := bytes.NewBuffer([]byte{})
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
@@ -68,7 +77,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
}
|
||||
}
|
||||
{
|
||||
cmd := exec.Command(path, "create", this.config.BlackName, "hash:ip", "timeout", "0")
|
||||
cmd := exec.Command(path, "create", this.config.BlackName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
||||
stderr := bytes.NewBuffer([]byte{})
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
@@ -163,24 +172,39 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
}
|
||||
|
||||
{
|
||||
cmd := exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.WhiteName, "src", "-j", "ACCEPT")
|
||||
stderr := bytes.NewBuffer([]byte{})
|
||||
cmd.Stderr = stderr
|
||||
// 检查规则是否存在
|
||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", this.config.WhiteName, "src", "-j", "ACCEPT")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
output := stderr.Bytes()
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||
var exists = err == nil
|
||||
|
||||
// 添加规则
|
||||
if !exists {
|
||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.WhiteName, "src", "-j", "ACCEPT")
|
||||
stderr := bytes.NewBuffer([]byte{})
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
output := stderr.Bytes()
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
cmd := exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.BlackName, "src", "-j", "REJECT")
|
||||
stderr := bytes.NewBuffer([]byte{})
|
||||
cmd.Stderr = stderr
|
||||
// 检查规则是否存在
|
||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", this.config.BlackName, "src", "-j", "REJECT")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
output := stderr.Bytes()
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||
var exists = err == nil
|
||||
|
||||
if !exists {
|
||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.BlackName, "src", "-j", "REJECT")
|
||||
stderr := bytes.NewBuffer([]byte{})
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
output := stderr.Bytes()
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +236,16 @@ func (this *IPSetAction) runAction(action string, listType IPListType, item *pb.
|
||||
return nil
|
||||
}
|
||||
for _, cidr := range cidrList {
|
||||
index := strings.Index(cidr, "/")
|
||||
if index <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 只支持/24以下的
|
||||
if types.Int(cidr[index+1:]) < 24 {
|
||||
continue
|
||||
}
|
||||
|
||||
item.IpFrom = cidr
|
||||
item.IpTo = ""
|
||||
err := this.runActionSingleIP(action, listType, item)
|
||||
@@ -246,6 +280,11 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("ipset")
|
||||
if err != nil {
|
||||
// 找不到ipset命令错误只提示一次
|
||||
if this.ipsetNotfound {
|
||||
return nil
|
||||
}
|
||||
this.ipsetNotfound = true
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -258,19 +297,30 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
case "deleteItem":
|
||||
args = append(args, "del")
|
||||
}
|
||||
args = append(args, listName, item.IpFrom)
|
||||
timestamp := time.Now().Unix()
|
||||
if item.ExpiredAt > timestamp {
|
||||
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10))
|
||||
}
|
||||
|
||||
//logs.Println(args)
|
||||
args = append(args, listName, item.IpFrom)
|
||||
if action == "addItem" {
|
||||
timestamp := time.Now().Unix()
|
||||
if item.ExpiredAt > timestamp {
|
||||
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10))
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
// MAC OS直接返回
|
||||
return nil
|
||||
}
|
||||
|
||||
this.errorBuf.Reset()
|
||||
cmd := exec.Command(path, args...)
|
||||
return cmd.Run()
|
||||
cmd.Stderr = this.errorBuf
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
var errString = this.errorBuf.String()
|
||||
if action == "deleteItem" && strings.Contains(errString, "not added") {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.TrimSpace(errString))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,15 +9,18 @@ import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// IPTables动作
|
||||
// IPTablesAction IPTables动作
|
||||
// 相关命令:
|
||||
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT
|
||||
// iptables -A INPUT -s "192.168.2.32" -j REJECT
|
||||
// iptables -D ...
|
||||
// iptables -D INPUT ...
|
||||
// iptables -F INPUT
|
||||
type IPTablesAction struct {
|
||||
BaseAction
|
||||
|
||||
config *firewallconfigs.FirewallActionIPTablesConfig
|
||||
|
||||
iptablesNotFound bool
|
||||
}
|
||||
|
||||
func NewIPTablesAction() *IPTablesAction {
|
||||
@@ -76,6 +79,10 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
if this.iptablesNotFound {
|
||||
return nil
|
||||
}
|
||||
this.iptablesNotFound = true
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
var SharedActionManager = NewActionManager()
|
||||
|
||||
// 动作管理器定义
|
||||
// ActionManager 动作管理器定义
|
||||
type ActionManager struct {
|
||||
locker sync.Mutex
|
||||
|
||||
@@ -23,7 +23,7 @@ type ActionManager struct {
|
||||
instanceMap map[int64]ActionInterface // id => instance
|
||||
}
|
||||
|
||||
// 获取动作管理对象
|
||||
// NewActionManager 获取动作管理对象
|
||||
func NewActionManager() *ActionManager {
|
||||
return &ActionManager{
|
||||
configMap: map[int64]*firewallconfigs.FirewallActionConfig{},
|
||||
@@ -31,7 +31,7 @@ func NewActionManager() *ActionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
// UpdateActions 更新配置
|
||||
func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActionConfig) {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
@@ -108,14 +108,14 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
|
||||
}
|
||||
}
|
||||
|
||||
// 查找事件对应的动作
|
||||
// FindEventActions 查找事件对应的动作
|
||||
func (this *ActionManager) FindEventActions(eventLevel string) []ActionInterface {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
return this.eventMap[eventLevel]
|
||||
}
|
||||
|
||||
// 执行添加IP动作
|
||||
// AddItem 执行添加IP动作
|
||||
func (this *ActionManager) AddItem(listType IPListType, item *pb.IPItem) {
|
||||
instances, ok := this.eventMap[item.EventLevel]
|
||||
if ok {
|
||||
@@ -128,7 +128,7 @@ func (this *ActionManager) AddItem(listType IPListType, item *pb.IPItem) {
|
||||
}
|
||||
}
|
||||
|
||||
// 执行删除IP动作
|
||||
// DeleteItem 执行删除IP动作
|
||||
func (this *ActionManager) DeleteItem(listType IPListType, item *pb.IPItem) {
|
||||
instances, ok := this.eventMap[item.EventLevel]
|
||||
if ok {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 脚本命令动作
|
||||
// ScriptAction 脚本命令动作
|
||||
type ScriptAction struct {
|
||||
BaseAction
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func (this *IPItem) Contains(ip uint64) bool {
|
||||
case IPItemTypeIPv6:
|
||||
return this.containsIPv6(ip)
|
||||
case IPItemTypeAll:
|
||||
return this.containsAll(ip)
|
||||
return this.containsAll()
|
||||
default:
|
||||
return this.containsIPv4(ip)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (this *IPItem) containsIPv6(ip uint64) bool {
|
||||
}
|
||||
|
||||
// 检查是否包所有IP
|
||||
func (this *IPItem) containsAll(ip uint64) bool {
|
||||
func (this *IPItem) containsAll() bool {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < utils.UnixTime() {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var GlobalBlackIPList = NewIPList()
|
||||
var GlobalWhiteIPList = NewIPList()
|
||||
|
||||
// IPList IP名单
|
||||
// TODO IP名单可以分片关闭,这样让每一片的数据量减少,查询更快
|
||||
type IPList struct {
|
||||
|
||||
145
internal/iplibrary/ip_list_db.go
Normal file
145
internal/iplibrary/ip_list_db.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type IPListDB struct {
|
||||
db *sql.DB
|
||||
|
||||
itemTableName string
|
||||
deleteItemStmt *sql.Stmt
|
||||
insertItemStmt *sql.Stmt
|
||||
selectItemsStmt *sql.Stmt
|
||||
|
||||
dir string
|
||||
}
|
||||
|
||||
func NewIPListDB() (*IPListDB, error) {
|
||||
var db = &IPListDB{
|
||||
itemTableName: "ipItems",
|
||||
dir: filepath.Clean(Tea.Root + "/data"),
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *IPListDB) init() error {
|
||||
// 检查目录是否存在
|
||||
_, err := os.Stat(this.dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(this.dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'")
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", "file:"+this.dir+"/ip_list.db?cache=shared&mode=rwc&_journal_mode=WAL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
this.db = db
|
||||
|
||||
// 初始化数据库
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"listId" integer DEFAULT 0,
|
||||
"listType" varchar(32),
|
||||
"isGlobal" integer(1) DEFAULT 0,
|
||||
"type" varchar(16),
|
||||
"itemId" integer DEFAULT 0,
|
||||
"ipFrom" varchar(64) DEFAULT 0,
|
||||
"ipTo" varchar(64) DEFAULT 0,
|
||||
"expiredAt" integer DEFAULT 0,
|
||||
"eventLevel" varchar(32),
|
||||
"isDeleted" integer(1) DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"nodeId" integer DEFAULT 0,
|
||||
"serverId" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"itemId" ASC
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"expiredAt" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化SQL语句
|
||||
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.db = db
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
||||
_, err := this.deleteItemStmt.Exec(item.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, 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
|
||||
}
|
||||
|
||||
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
|
||||
rows, err := this.selectItemsStmt.Query(offset, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
|
||||
var pbItem = &pb.IPItem{}
|
||||
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, pbItem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *IPListDB) Close() error {
|
||||
if this.db != nil {
|
||||
_ = this.deleteItemStmt.Close()
|
||||
_ = this.insertItemStmt.Close()
|
||||
_ = this.selectItemsStmt.Close()
|
||||
|
||||
return this.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
60
internal/iplibrary/ip_list_db_test.go
Normal file
60
internal/iplibrary/ip_list_db_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPListDB_AddItem(t *testing.T) {
|
||||
db, err := NewIPListDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = db.AddItem(&pb.IPItem{
|
||||
Id: 1,
|
||||
IpFrom: "192.168.1.101",
|
||||
IpTo: "",
|
||||
Version: 1024,
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
Reason: "",
|
||||
ListId: 2,
|
||||
IsDeleted: true,
|
||||
Type: "ipv4",
|
||||
EventLevel: "error",
|
||||
ListType: "black",
|
||||
IsGlobal: true,
|
||||
CreatedAt: 0,
|
||||
NodeId: 11,
|
||||
ServerId: 22,
|
||||
SourceNodeId: 0,
|
||||
SourceServerId: 0,
|
||||
SourceHTTPFirewallPolicyId: 0,
|
||||
SourceHTTPFirewallRuleGroupId: 0,
|
||||
SourceHTTPFirewallRuleSetId: 0,
|
||||
SourceServer: nil,
|
||||
SourceHTTPFirewallPolicy: nil,
|
||||
SourceHTTPFirewallRuleGroup: nil,
|
||||
SourceHTTPFirewallRuleSet: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestIPListDB_ReadItems(t *testing.T) {
|
||||
db, err := NewIPListDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
items, err := db.ReadItems(0, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logs.PrintAsJSON(items, t)
|
||||
}
|
||||
55
internal/iplibrary/list_utils.go
Normal file
55
internal/iplibrary/list_utils.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
)
|
||||
|
||||
// AllowIP 检查IP是否被允许访问
|
||||
func AllowIP(ip string, serverId int64) bool {
|
||||
var ipLong = utils.IP2Long(ip)
|
||||
if ipLong == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// check white lists
|
||||
if GlobalWhiteIPList.Contains(ipLong) {
|
||||
return true
|
||||
}
|
||||
|
||||
if serverId > 0 {
|
||||
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
||||
if list != nil && list.Contains(ipLong) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// check black lists
|
||||
if GlobalBlackIPList.Contains(ipLong) {
|
||||
return false
|
||||
}
|
||||
|
||||
if serverId > 0 {
|
||||
var list = SharedServerListManager.FindBlackList(serverId, false)
|
||||
if list != nil && list.Contains(ipLong) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AllowIPStrings 检查一组IP是否被允许访问
|
||||
func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
||||
if len(ipStrings) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, ip := range ipStrings {
|
||||
isAllowed := AllowIP(ip, serverId)
|
||||
if !isAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
20
internal/iplibrary/list_utils_test.go
Normal file
20
internal/iplibrary/list_utils_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPIsAllowed(t *testing.T) {
|
||||
manager := NewIPListManager()
|
||||
manager.init()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
t.Log(AllowIP("127.0.0.1", 0))
|
||||
t.Log(AllowIP("127.0.0.1", 23))
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func init() {
|
||||
// 初始化
|
||||
library, err := SharedManager.Load()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIBRARY", err.Error())
|
||||
remotelogs.ErrorObject("IP_LIBRARY", err)
|
||||
return
|
||||
}
|
||||
SharedLibrary = library
|
||||
|
||||
@@ -46,13 +46,13 @@ func (this *CountryManager) Start() {
|
||||
// 从缓存中读取
|
||||
err := this.load()
|
||||
if err != nil {
|
||||
remotelogs.Error("COUNTRY_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("COUNTRY_MANAGER", err)
|
||||
}
|
||||
|
||||
// 第一次更新
|
||||
err = this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("COUNTRY_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("COUNTRY_MANAGER", err)
|
||||
}
|
||||
|
||||
// 定时更新
|
||||
@@ -63,7 +63,7 @@ func (this *CountryManager) Start() {
|
||||
for range ticker.C {
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("COUNTRY_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("COUNTRY_MANAGER", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -22,9 +23,7 @@ func init() {
|
||||
|
||||
// IPListManager IP名单管理
|
||||
type IPListManager struct {
|
||||
// 缓存文件
|
||||
// 每行一个数据:id|from|to|expiredAt
|
||||
cacheFile string
|
||||
db *IPListDB
|
||||
|
||||
version int64
|
||||
pageSize int64
|
||||
@@ -35,22 +34,24 @@ type IPListManager struct {
|
||||
|
||||
func NewIPListManager() *IPListManager {
|
||||
return &IPListManager{
|
||||
cacheFile: Tea.Root + "/configs/ip_list.cache",
|
||||
pageSize: 1000,
|
||||
listMap: map[int64]*IPList{},
|
||||
pageSize: 500,
|
||||
listMap: map[int64]*IPList{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPListManager) Start() {
|
||||
// TODO 从缓存当中读取数据
|
||||
this.init()
|
||||
|
||||
// 第一次读取
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("IP_LIST_MANAGER", err)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(60 * time.Second)
|
||||
if Tea.IsTesting() {
|
||||
ticker = time.NewTicker(10 * time.Second)
|
||||
}
|
||||
events.On(events.EventQuit, func() {
|
||||
ticker.Stop()
|
||||
})
|
||||
@@ -64,7 +65,7 @@ func (this *IPListManager) Start() {
|
||||
if err != nil {
|
||||
countErrors++
|
||||
|
||||
remotelogs.Error("IP_LIST_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("IP_LIST_MANAGER", err)
|
||||
|
||||
// 连续错误小于3次的我们立即重试
|
||||
if countErrors <= 3 {
|
||||
@@ -79,6 +80,31 @@ func (this *IPListManager) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPListManager) init() {
|
||||
// 从数据库中当中读取数据
|
||||
db, err := NewIPListDB()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "create ip list local database failed: "+err.Error())
|
||||
} else {
|
||||
this.db = db
|
||||
|
||||
var offset int64 = 0
|
||||
var size int64 = 1000
|
||||
for {
|
||||
items, err := db.ReadItems(offset, size)
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "read ip list from local database failed: "+err.Error())
|
||||
} else {
|
||||
if len(items) == 0 {
|
||||
break
|
||||
}
|
||||
this.processItems(items, false)
|
||||
}
|
||||
offset += int64(len(items))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPListManager) loop() error {
|
||||
for {
|
||||
hasNext, err := this.fetch()
|
||||
@@ -88,10 +114,9 @@ func (this *IPListManager) loop() error {
|
||||
if !hasNext {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// TODO 写入到缓存当中
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -111,11 +136,53 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
if len(items) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 保存到本地数据库
|
||||
if this.db != nil {
|
||||
for _, item := range items {
|
||||
err = this.db.AddItem(item)
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "insert item to local database failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.processItems(items, true)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (this *IPListManager) FindList(listId int64) *IPList {
|
||||
this.locker.Lock()
|
||||
list, _ := this.listMap[listId]
|
||||
this.locker.Unlock()
|
||||
return list
|
||||
}
|
||||
|
||||
func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool) {
|
||||
this.locker.Lock()
|
||||
var changedLists = map[*IPList]bool{}
|
||||
for _, item := range items {
|
||||
list, ok := this.listMap[item.ListId]
|
||||
if !ok {
|
||||
var list *IPList
|
||||
// TODO 实现节点专有List
|
||||
if item.ServerId > 0 { // 服务专有List
|
||||
switch item.ListType {
|
||||
case "black":
|
||||
list = SharedServerListManager.FindBlackList(item.ServerId, true)
|
||||
case "white":
|
||||
list = SharedServerListManager.FindWhiteList(item.ServerId, true)
|
||||
}
|
||||
} else if item.IsGlobal { // 全局List
|
||||
switch item.ListType {
|
||||
case "black":
|
||||
list = GlobalBlackIPList
|
||||
case "white":
|
||||
list = GlobalWhiteIPList
|
||||
}
|
||||
} else { // 其他List
|
||||
list = this.listMap[item.ListId]
|
||||
}
|
||||
if list == nil {
|
||||
list = NewIPList()
|
||||
this.listMap[item.ListId] = list
|
||||
}
|
||||
@@ -125,8 +192,13 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
if item.IsDeleted {
|
||||
list.Delete(item.Id)
|
||||
|
||||
// 从WAF名单中删除
|
||||
waf.SharedIPBlackList.RemoveIP(item.IpFrom, item.ServerId)
|
||||
|
||||
// 操作事件
|
||||
SharedActionManager.DeleteItem(item.ListType, item)
|
||||
if shouldExecute {
|
||||
SharedActionManager.DeleteItem(item.ListType, item)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -141,8 +213,10 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
})
|
||||
|
||||
// 事件操作
|
||||
SharedActionManager.DeleteItem(item.ListType, item)
|
||||
SharedActionManager.AddItem(item.ListType, item)
|
||||
if shouldExecute {
|
||||
SharedActionManager.DeleteItem(item.ListType, item)
|
||||
SharedActionManager.AddItem(item.ListType, item)
|
||||
}
|
||||
}
|
||||
|
||||
for changedList := range changedLists {
|
||||
@@ -151,13 +225,4 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
|
||||
this.locker.Unlock()
|
||||
this.version = items[len(items)-1].Version
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (this *IPListManager) FindList(listId int64) *IPList {
|
||||
this.locker.Lock()
|
||||
list, _ := this.listMap[listId]
|
||||
this.locker.Unlock()
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
package iplibrary
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPListManager_init(t *testing.T) {
|
||||
manager := NewIPListManager()
|
||||
manager.init()
|
||||
t.Log(manager.listMap)
|
||||
t.Log(SharedServerListManager.blackMap)
|
||||
logs.PrintAsJSON(GlobalBlackIPList.sortedItems, t)
|
||||
}
|
||||
|
||||
func TestIPListManager_check(t *testing.T) {
|
||||
manager := NewIPListManager()
|
||||
manager.init()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
t.Log(SharedServerListManager.FindBlackList(23, true).Contains(utils.IP2Long("127.0.0.2")))
|
||||
t.Log(GlobalBlackIPList.Contains(utils.IP2Long("127.0.0.6")))
|
||||
}
|
||||
|
||||
func TestIPListManager_loop(t *testing.T) {
|
||||
manager := NewIPListManager()
|
||||
manager.pageSize = 2
|
||||
manager.Start()
|
||||
manager.pageSize = 10
|
||||
err := manager.loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -50,13 +50,13 @@ func (this *ProvinceManager) Start() {
|
||||
// 从缓存中读取
|
||||
err := this.load()
|
||||
if err != nil {
|
||||
remotelogs.Error("PROVINCE_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("PROVINCE_MANAGER", err)
|
||||
}
|
||||
|
||||
// 第一次更新
|
||||
err = this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("PROVINCE_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("PROVINCE_MANAGER", err)
|
||||
}
|
||||
|
||||
// 定时更新
|
||||
@@ -67,7 +67,7 @@ func (this *ProvinceManager) Start() {
|
||||
for range ticker.C {
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("PROVINCE_MANAGER", err.Error())
|
||||
remotelogs.ErrorObject("PROVINCE_MANAGER", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
internal/iplibrary/server_list_manager.go
Normal file
61
internal/iplibrary/server_list_manager.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import "sync"
|
||||
|
||||
var SharedServerListManager = NewServerListManager()
|
||||
|
||||
// ServerListManager 服务相关名单
|
||||
type ServerListManager struct {
|
||||
whiteMap map[int64]*IPList // serverId => *List
|
||||
blackMap map[int64]*IPList // serverId => *List
|
||||
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewServerListManager() *ServerListManager {
|
||||
return &ServerListManager{
|
||||
whiteMap: map[int64]*IPList{},
|
||||
blackMap: map[int64]*IPList{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ServerListManager) FindWhiteList(serverId int64, autoCreate bool) *IPList {
|
||||
this.locker.RLock()
|
||||
list, ok := this.whiteMap[serverId]
|
||||
this.locker.RUnlock()
|
||||
if ok {
|
||||
return list
|
||||
}
|
||||
|
||||
if autoCreate {
|
||||
list = NewIPList()
|
||||
this.locker.Lock()
|
||||
this.whiteMap[serverId] = list
|
||||
this.locker.Unlock()
|
||||
|
||||
return list
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *ServerListManager) FindBlackList(serverId int64, autoCreate bool) *IPList {
|
||||
this.locker.RLock()
|
||||
list, ok := this.blackMap[serverId]
|
||||
this.locker.RUnlock()
|
||||
if ok {
|
||||
return list
|
||||
}
|
||||
|
||||
if autoCreate {
|
||||
list = NewIPList()
|
||||
this.locker.Lock()
|
||||
this.blackMap[serverId] = list
|
||||
this.locker.Unlock()
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func (this *Updater) Start() {
|
||||
for range ticker.C {
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIBRARY", err.Error())
|
||||
remotelogs.ErrorObject("IP_LIBRARY", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@@ -164,6 +165,8 @@ func (this *Task) Start() error {
|
||||
this.statsTicker = utils.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for this.statsTicker.Next() {
|
||||
var tr = trackers.Begin("[METRIC]DUMP_STATS_TO_LOCAL_DATABASE")
|
||||
|
||||
this.statsLocker.Lock()
|
||||
var statsMap = this.statsMap
|
||||
this.statsMap = map[string]*Stat{}
|
||||
@@ -175,6 +178,8 @@ func (this *Task) Start() error {
|
||||
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tr.End()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -182,7 +187,9 @@ func (this *Task) Start() error {
|
||||
this.cleanTicker = utils.NewTicker(24 * time.Hour)
|
||||
go func() {
|
||||
for this.cleanTicker.Next() {
|
||||
var tr = trackers.Begin("[METRIC]CLEAN_EXPIRED")
|
||||
err := this.CleanExpired()
|
||||
tr.End()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
|
||||
}
|
||||
@@ -193,7 +200,9 @@ func (this *Task) Start() error {
|
||||
this.uploadTicker = utils.NewTicker(this.item.UploadDuration())
|
||||
go func() {
|
||||
for this.uploadTicker.Next() {
|
||||
var tr = trackers.Begin("[METRIC]UPLOAD_STATS")
|
||||
err := this.Upload(1 * time.Second)
|
||||
tr.End()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (this *ValueQueue) Start() {
|
||||
// 这里单次循环就行,因为Loop里已经使用了Range通道
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("MONITOR_QUEUE", err.Error())
|
||||
remotelogs.ErrorObject("MONITOR_QUEUE", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ func (this *ValueQueue) Loop() error {
|
||||
CreatedAt: value.CreatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Error("MONITOR", err.Error())
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Warn("MONITOR", err.Error())
|
||||
} else {
|
||||
remotelogs.Error("MONITOR", err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
28
internal/monitor/value_queue_test.go
Normal file
28
internal/monitor/value_queue_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc/status"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValueQueue_RPC(t *testing.T) {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = rpcClient.NodeValueRPC().CreateNodeValue(rpcClient.Context(), &pb.CreateNodeValueRequest{})
|
||||
if err != nil {
|
||||
statusErr, ok:= status.FromError(err)
|
||||
if ok {
|
||||
logs.Println(statusErr.Code())
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
@@ -9,16 +9,18 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -45,7 +47,7 @@ func (this *APIStream) Start() {
|
||||
}
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("API_STREAM", err.Error())
|
||||
remotelogs.Warn("API_STREAM", err.Error())
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -79,7 +81,7 @@ func (this *APIStream) loop() error {
|
||||
|
||||
for {
|
||||
if isQuiting {
|
||||
logs.Println("API_STREAM", "quit")
|
||||
remotelogs.Println("API_STREAM", "quit")
|
||||
break
|
||||
}
|
||||
|
||||
@@ -112,6 +114,8 @@ func (this *APIStream) loop() error {
|
||||
err = this.handleNewNodeTask(message)
|
||||
case messageconfigs.MessageCodeCheckSystemdService: // 检查Systemd服务
|
||||
err = this.handleCheckSystemdService(message)
|
||||
case messageconfigs.MessageCodeChangeAPINode: // 修改API节点地址
|
||||
err = this.handleChangeAPINode(message)
|
||||
default:
|
||||
err = this.handleUnknownMessage(message)
|
||||
}
|
||||
@@ -571,6 +575,65 @@ func (this *APIStream) handleCheckSystemdService(message *pb.NodeStreamMessage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改API地址
|
||||
func (this *APIStream) handleChangeAPINode(message *pb.NodeStreamMessage) error {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "read config error: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
var messageData = &messageconfigs.ChangeAPINodeMessage{}
|
||||
err = json.Unmarshal(message.DataJSON, messageData)
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "unmarshal message failed: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = url.Parse(messageData.Addr)
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "invalid new api node address: '"+messageData.Addr+"'")
|
||||
return nil
|
||||
}
|
||||
|
||||
config.RPC.Endpoints = []string{messageData.Addr}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "save config file failed: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
this.replyOk(message.RequestId, "")
|
||||
|
||||
go func() {
|
||||
// 延后生效,防止变更前的API无法读取到状态
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
remotelogs.Error("API_STREAM", "change rpc endpoint to '"+
|
||||
messageData.Addr+"' failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rpcClient.Close()
|
||||
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
if err != nil {
|
||||
remotelogs.Error("API_STREAM", "change rpc endpoint to '"+
|
||||
messageData.Addr+"' failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
remotelogs.Println("API_STREAM", "change rpc endpoint to '"+
|
||||
messageData.Addr+"' successfully")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理未知消息
|
||||
func (this *APIStream) handleUnknownMessage(message *pb.NodeStreamMessage) error {
|
||||
this.replyFail(message.RequestId, "unknown message code '"+message.Code+"'")
|
||||
|
||||
103
internal/nodes/client_conn.go
Normal file
103
internal/nodes/client_conn.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 发送监控流量
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
// 加入到数据队列中
|
||||
if teaconst.InTrafficBytes > 0 {
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficIn, maps.Map{
|
||||
"total": teaconst.InTrafficBytes,
|
||||
})
|
||||
}
|
||||
if teaconst.OutTrafficBytes > 0 {
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficOut, maps.Map{
|
||||
"total": teaconst.OutTrafficBytes,
|
||||
})
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
atomic.StoreUint64(&teaconst.InTrafficBytes, 0)
|
||||
atomic.StoreUint64(&teaconst.OutTrafficBytes, 0)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// ClientConn 客户端连接
|
||||
type ClientConn struct {
|
||||
rawConn net.Conn
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewClientConn(conn net.Conn, quickClose bool) net.Conn {
|
||||
if quickClose {
|
||||
tcpConn, ok := conn.(*net.TCPConn)
|
||||
if ok {
|
||||
// TODO 可以设置此值
|
||||
_ = tcpConn.SetLinger(3)
|
||||
}
|
||||
}
|
||||
|
||||
return &ClientConn{rawConn: conn}
|
||||
}
|
||||
|
||||
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||
n, err = this.rawConn.Read(b)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ClientConn) Write(b []byte) (n int, err error) {
|
||||
n, err = this.rawConn.Write(b)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&teaconst.OutTrafficBytes, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ClientConn) Close() error {
|
||||
this.isClosed = true
|
||||
return this.rawConn.Close()
|
||||
}
|
||||
|
||||
func (this *ClientConn) LocalAddr() net.Addr {
|
||||
return this.rawConn.LocalAddr()
|
||||
}
|
||||
|
||||
func (this *ClientConn) RemoteAddr() net.Addr {
|
||||
return this.rawConn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (this *ClientConn) SetDeadline(t time.Time) error {
|
||||
return this.rawConn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (this *ClientConn) SetReadDeadline(t time.Time) error {
|
||||
return this.rawConn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (this *ClientConn) SetWriteDeadline(t time.Time) error {
|
||||
return this.rawConn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (this *ClientConn) IsClosed() bool {
|
||||
return this.isClosed
|
||||
}
|
||||
22
internal/nodes/client_conn_utils.go
Normal file
22
internal/nodes/client_conn_utils.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// 判断客户端连接是否已关闭
|
||||
func isClientConnClosed(conn net.Conn) bool {
|
||||
if conn == nil {
|
||||
return true
|
||||
}
|
||||
clientConn, ok := conn.(*ClientConn)
|
||||
if ok {
|
||||
return clientConn.IsClosed()
|
||||
}
|
||||
|
||||
// TODO 解决tls.Conn无法获取底层连接对象的问题
|
||||
|
||||
return false
|
||||
}
|
||||
54
internal/nodes/client_listener.go
Normal file
54
internal/nodes/client_listener.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"net"
|
||||
)
|
||||
|
||||
// ClientListener 客户端网络监听
|
||||
type ClientListener struct {
|
||||
rawListener net.Listener
|
||||
quickClose bool
|
||||
}
|
||||
|
||||
func NewClientListener(listener net.Listener, quickClose bool) net.Listener {
|
||||
return &ClientListener{
|
||||
rawListener: listener,
|
||||
quickClose: quickClose,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ClientListener) Accept() (net.Conn, error) {
|
||||
conn, err := this.rawListener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 是否在WAF名单中
|
||||
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err == nil {
|
||||
if !iplibrary.AllowIP(ip, 0) || (!waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) &&
|
||||
waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)) {
|
||||
tcpConn, ok := conn.(*net.TCPConn)
|
||||
if ok {
|
||||
_ = tcpConn.SetLinger(0)
|
||||
}
|
||||
|
||||
_ = conn.Close()
|
||||
return this.Accept()
|
||||
}
|
||||
}
|
||||
|
||||
return NewClientConn(conn, this.quickClose), nil
|
||||
}
|
||||
|
||||
func (this *ClientListener) Close() error {
|
||||
return this.rawListener.Close()
|
||||
}
|
||||
|
||||
func (this *ClientListener) Addr() net.Addr {
|
||||
return this.rawListener.Addr()
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -12,13 +14,15 @@ var sharedHTTPAccessLogQueue = NewHTTPAccessLogQueue()
|
||||
// HTTPAccessLogQueue HTTP访问日志队列
|
||||
type HTTPAccessLogQueue struct {
|
||||
queue chan *pb.HTTPAccessLog
|
||||
|
||||
rpcClient *rpc.RPCClient
|
||||
}
|
||||
|
||||
// NewHTTPAccessLogQueue 获取新对象
|
||||
func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
|
||||
// 队列中最大的值,超出此数量的访问日志会被丢弃
|
||||
// TODO 需要可以在界面中设置
|
||||
maxSize := 10000
|
||||
maxSize := 20000
|
||||
queue := &HTTPAccessLogQueue{
|
||||
queue: make(chan *pb.HTTPAccessLog, maxSize),
|
||||
}
|
||||
@@ -49,17 +53,31 @@ func (this *HTTPAccessLogQueue) Push(accessLog *pb.HTTPAccessLog) {
|
||||
|
||||
// 上传访问日志
|
||||
func (this *HTTPAccessLogQueue) loop() error {
|
||||
accessLogs := []*pb.HTTPAccessLog{}
|
||||
count := 0
|
||||
var accessLogs = []*pb.HTTPAccessLog{}
|
||||
var count = 0
|
||||
var timestamp int64
|
||||
var requestId = 1_000_000
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case accessLog := <-this.queue:
|
||||
var unixTime = utils.UnixTime()
|
||||
if unixTime > timestamp {
|
||||
requestId = 1_000_000
|
||||
timestamp = unixTime
|
||||
} else {
|
||||
requestId++
|
||||
}
|
||||
|
||||
// timestamp + requestId + nodeId
|
||||
accessLog.RequestId = strconv.FormatInt(unixTime, 10) + strconv.Itoa(requestId) + strconv.FormatInt(accessLog.NodeId, 10)
|
||||
|
||||
accessLogs = append(accessLogs, accessLog)
|
||||
count++
|
||||
|
||||
// 每次只提交 N 条访问日志,防止网络拥堵
|
||||
if count > 1000 {
|
||||
if count > 2000 {
|
||||
break Loop
|
||||
}
|
||||
default:
|
||||
@@ -72,12 +90,15 @@ Loop:
|
||||
}
|
||||
|
||||
// 发送到API
|
||||
client, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
if this.rpcClient == nil {
|
||||
client, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.rpcClient = client
|
||||
}
|
||||
|
||||
_, err = client.HTTPAccessLogRPC().CreateHTTPAccessLogs(client.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
|
||||
_, err := this.rpcClient.HTTPAccessLogRPC().CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -37,7 +39,7 @@ func NewHTTPClientPool() *HTTPClientPool {
|
||||
}
|
||||
|
||||
// Client 根据地址获取客户端
|
||||
func (this *HTTPClientPool) Client(req *http.Request, origin *serverconfigs.OriginConfig, originAddr string) (rawClient *http.Client, err error) {
|
||||
func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.OriginConfig, originAddr string, proxyProtocol *serverconfigs.ProxyProtocolConfig) (rawClient *http.Client, err error) {
|
||||
if origin.Addr == nil {
|
||||
return nil, errors.New("origin addr should not be empty (originId:" + strconv.FormatInt(origin.Id, 10) + ")")
|
||||
}
|
||||
@@ -73,11 +75,11 @@ func (this *HTTPClientPool) Client(req *http.Request, origin *serverconfigs.Orig
|
||||
numberCPU = 8
|
||||
}
|
||||
if maxConnections <= 0 {
|
||||
maxConnections = numberCPU * 8
|
||||
maxConnections = numberCPU * 32
|
||||
}
|
||||
|
||||
if idleConns <= 0 {
|
||||
idleConns = numberCPU * 4
|
||||
idleConns = numberCPU * 8
|
||||
}
|
||||
//logs.Println("[ORIGIN]max connections:", maxConnections)
|
||||
|
||||
@@ -105,7 +107,7 @@ func (this *HTTPClientPool) Client(req *http.Request, origin *serverconfigs.Orig
|
||||
for i := 1; i <= retries; i++ {
|
||||
port := int(toaConfig.RandLocalPort())
|
||||
// TODO 思考是否支持X-Real-IP/X-Forwarded-IP
|
||||
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + req.RemoteAddr)
|
||||
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + req.requestRemoteAddr(true))
|
||||
if err != nil {
|
||||
remotelogs.Error("TOA", "add failed: "+err.Error())
|
||||
} else {
|
||||
@@ -126,10 +128,43 @@ func (this *HTTPClientPool) Client(req *http.Request, origin *serverconfigs.Orig
|
||||
}
|
||||
|
||||
// 普通的连接
|
||||
return (&net.Dialer{
|
||||
conn, err := (&net.Dialer{
|
||||
Timeout: connectionTimeout,
|
||||
KeepAlive: 1 * time.Minute,
|
||||
}).DialContext(ctx, network, originAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
var remoteAddr = req.requestRemoteAddr(true)
|
||||
var transportProtocol = proxyproto.TCPv4
|
||||
if strings.Contains(remoteAddr, ":") {
|
||||
transportProtocol = proxyproto.TCPv6
|
||||
}
|
||||
var destAddr = conn.RemoteAddr()
|
||||
var reqConn = req.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if reqConn != nil {
|
||||
destAddr = reqConn.(net.Conn).LocalAddr()
|
||||
}
|
||||
header := proxyproto.Header{
|
||||
Version: byte(proxyProtocol.Version),
|
||||
Command: proxyproto.PROXY,
|
||||
TransportProtocol: transportProtocol,
|
||||
SourceAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP(remoteAddr),
|
||||
Port: req.requestRemotePort(),
|
||||
},
|
||||
DestinationAddr: destAddr,
|
||||
}
|
||||
_, err = header.WriteTo(conn)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
},
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: idleConns,
|
||||
|
||||
@@ -21,14 +21,14 @@ func TestHTTPClientPool_Client(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
{
|
||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress())
|
||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("client:", client)
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress())
|
||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
t.Log("get", i)
|
||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress())
|
||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,6 @@ func BenchmarkHTTPClientPool_Client(b *testing.B) {
|
||||
|
||||
pool := NewHTTPClientPool()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress())
|
||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,13 +86,21 @@ type HTTPRequest struct {
|
||||
// 初始化
|
||||
func (this *HTTPRequest) init() {
|
||||
this.writer = NewHTTPWriter(this, this.RawWriter)
|
||||
this.web = &serverconfigs.HTTPWebConfig{IsOn: true}
|
||||
this.web = &serverconfigs.HTTPWebConfig{
|
||||
IsOn: true,
|
||||
}
|
||||
|
||||
// this.uri = this.RawReq.URL.RequestURI()
|
||||
// 之所以不使用RequestURI(),是不想让URL中的Path被Encode
|
||||
var urlPath = this.RawReq.URL.Path
|
||||
if this.Server.Web != nil && this.Server.Web.MergeSlashes {
|
||||
urlPath = utils.CleanPath(urlPath)
|
||||
this.web.MergeSlashes = true
|
||||
}
|
||||
if len(this.RawReq.URL.RawQuery) > 0 {
|
||||
this.uri = this.RawReq.URL.Path + "?" + this.RawReq.URL.RawQuery
|
||||
this.uri = urlPath + "?" + this.RawReq.URL.RawQuery
|
||||
} else {
|
||||
this.uri = this.RawReq.URL.Path
|
||||
this.uri = urlPath
|
||||
}
|
||||
|
||||
this.rawURI = this.uri
|
||||
@@ -126,6 +134,31 @@ func (this *HTTPRequest) Do() {
|
||||
return
|
||||
}
|
||||
|
||||
// 特殊URL处理
|
||||
if len(this.rawURI) > 1 && this.rawURI[1] == '.' {
|
||||
// ACME
|
||||
// TODO 需要配置是否启用ACME检测
|
||||
if strings.HasPrefix(this.rawURI, "/.well-known/acme-challenge/") {
|
||||
this.doACME()
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 套餐
|
||||
if this.Server.UserPlan != nil && !this.Server.UserPlan.IsAvailable() {
|
||||
this.doPlanExpires()
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
|
||||
// 流量限制
|
||||
if this.Server.TrafficLimit != nil && this.Server.TrafficLimit.IsOn && !this.Server.TrafficLimit.IsEmpty() && this.Server.TrafficLimitStatus != nil && this.Server.TrafficLimitStatus.IsValid() {
|
||||
this.doTrafficLimit()
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
|
||||
// WAF
|
||||
if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn {
|
||||
if this.doWAFRequest() {
|
||||
@@ -167,12 +200,10 @@ func (this *HTTPRequest) Do() {
|
||||
|
||||
// 开始调用
|
||||
func (this *HTTPRequest) doBegin() {
|
||||
// 特殊URL处理
|
||||
if len(this.rawURI) > 1 && this.rawURI[1] == '.' {
|
||||
// ACME
|
||||
// TODO 需要配置是否启用ACME检测
|
||||
if strings.HasPrefix(this.rawURI, "/.well-known/acme-challenge/") {
|
||||
this.doACME()
|
||||
// 处理健康检查
|
||||
var healthCheckKey = this.RawReq.Header.Get(serverconfigs.HealthCheckHeaderName)
|
||||
if len(healthCheckKey) > 0 {
|
||||
if this.doHealthCheck(healthCheckKey) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -246,14 +277,15 @@ func (this *HTTPRequest) doEnd() {
|
||||
|
||||
// 流量统计
|
||||
// TODO 增加是否开启开关
|
||||
// TODO 增加Header统计,考虑从Conn中读取
|
||||
if this.Server != nil {
|
||||
if this.isCached {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, this.writer.sentBodyBytes, 1, 1, 0, 0)
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, this.writer.sentBodyBytes, 1, 1, 0, 0, this.Server.ShouldCheckTrafficLimit(), this.Server.PlanId())
|
||||
} else {
|
||||
if this.isAttack {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 1, this.writer.sentBodyBytes)
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 1, this.writer.sentBodyBytes, this.Server.ShouldCheckTrafficLimit(), this.Server.PlanId())
|
||||
} else {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 0, 0)
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 0, 0, this.Server.ShouldCheckTrafficLimit(), this.Server.PlanId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package nodes
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -109,6 +111,32 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是否在Purge
|
||||
if this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey {
|
||||
err := storage.Delete(key)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err == nil {
|
||||
for _, rpcServerService := range rpcClient.ServerRPCList() {
|
||||
_, err = rpcServerService.PurgeServerCache(rpcClient.Context(), &pb.PurgeServerCacheRequest{
|
||||
Domains: []string{this.Host},
|
||||
Keys: []string{key},
|
||||
Prefixes: nil,
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
buf := bytePool32k.Get()
|
||||
defer func() {
|
||||
bytePool32k.Put(buf)
|
||||
|
||||
27
internal/nodes/http_request_health_check.go
Normal file
27
internal/nodes/http_request_health_check.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
)
|
||||
|
||||
// 健康检查
|
||||
func (this *HTTPRequest) doHealthCheck(key string) (stop bool) {
|
||||
this.tags = append(this.tags, "healthCheck")
|
||||
|
||||
this.RawReq.Header.Del(serverconfigs.HealthCheckHeaderName)
|
||||
|
||||
data, err := nodeutils.DecryptData(sharedNodeConfig.NodeId, sharedNodeConfig.Secret, key)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_HEALTH_CHECK", "decode key failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if data.GetBool("onlyBasicRequest") {
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -8,7 +9,11 @@ import (
|
||||
|
||||
// 主机地址快速跳转
|
||||
func (this *HTTPRequest) doHostRedirect() (blocked bool) {
|
||||
fullURL := this.requestScheme() + "://" + this.Host + this.RawReq.URL.Path
|
||||
var urlPath = this.RawReq.URL.Path
|
||||
if this.web.MergeSlashes {
|
||||
urlPath = utils.CleanPath(urlPath)
|
||||
}
|
||||
fullURL := this.requestScheme() + "://" + this.Host + urlPath
|
||||
for _, u := range this.web.HostRedirects {
|
||||
if !u.IsOn {
|
||||
continue
|
||||
|
||||
@@ -3,14 +3,10 @@ package nodes
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var requestId int64 = 1_0000_0000_0000_0000
|
||||
|
||||
// 日志
|
||||
func (this *HTTPRequest) log() {
|
||||
if this.disableLog {
|
||||
@@ -85,7 +81,7 @@ func (this *HTTPRequest) log() {
|
||||
}
|
||||
|
||||
accessLog := &pb.HTTPAccessLog{
|
||||
RequestId: strconv.FormatInt(this.requestFromTime.UnixNano(), 10) + strconv.FormatInt(atomic.AddInt64(&requestId, 1), 10) + sharedNodeConfig.PaddedId(),
|
||||
RequestId: "",
|
||||
NodeId: sharedNodeConfig.Id,
|
||||
ServerId: this.Server.Id,
|
||||
RemoteAddr: this.requestRemoteAddr(true),
|
||||
|
||||
@@ -23,7 +23,11 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
||||
if page.Match(status) {
|
||||
if len(page.BodyType) == 0 || page.BodyType == shared.BodyTypeURL {
|
||||
if urlPrefixRegexp.MatchString(page.URL) {
|
||||
this.doURL(http.MethodGet, page.URL, "", page.NewStatus, true)
|
||||
var newStatus = page.NewStatus
|
||||
if newStatus <= 0 {
|
||||
newStatus = status
|
||||
}
|
||||
this.doURL(http.MethodGet, page.URL, "", newStatus, true)
|
||||
return true
|
||||
} else {
|
||||
file := Tea.Root + Tea.DS + page.URL
|
||||
@@ -40,13 +44,28 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
msg := "404 could not read page content: '" + page.URL + "'"
|
||||
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
_, err := this.writer.Write([]byte(msg))
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.processResponseHeaders(page.NewStatus)
|
||||
this.writer.Prepare(stat.Size(), page.NewStatus)
|
||||
this.writer.WriteHeader(page.NewStatus)
|
||||
} else {
|
||||
this.processResponseHeaders(status)
|
||||
this.writer.Prepare(stat.Size(), status)
|
||||
this.writer.WriteHeader(status)
|
||||
}
|
||||
buf := bytePool1k.Get()
|
||||
@@ -69,17 +88,21 @@ func (this *HTTPRequest) doPage(status int) (shouldStop bool) {
|
||||
|
||||
return true
|
||||
} else if page.BodyType == shared.BodyTypeHTML {
|
||||
var content = this.Format(page.Body)
|
||||
|
||||
// 修改状态码
|
||||
if page.NewStatus > 0 {
|
||||
// 自定义响应Headers
|
||||
this.processResponseHeaders(page.NewStatus)
|
||||
this.writer.Prepare(int64(len(content)), page.NewStatus)
|
||||
this.writer.WriteHeader(page.NewStatus)
|
||||
} else {
|
||||
this.processResponseHeaders(status)
|
||||
this.writer.Prepare(int64(len(content)), status)
|
||||
this.writer.WriteHeader(status)
|
||||
}
|
||||
|
||||
_, err := this.writer.WriteString(this.Format(page.Body))
|
||||
_, err := this.writer.WriteString(content)
|
||||
if err != nil {
|
||||
if !this.canIgnore(err) {
|
||||
remotelogs.Warn("HTTP_REQUEST_PAGE", "write to client failed: "+err.Error())
|
||||
|
||||
19
internal/nodes/http_request_plan_expires.go
Normal file
19
internal/nodes/http_request_plan_expires.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 套餐过期
|
||||
func (this *HTTPRequest) doPlanExpires() {
|
||||
this.tags = append(this.tags, "plan")
|
||||
|
||||
var statusCode = http.StatusNotFound
|
||||
this.processResponseHeaders(statusCode)
|
||||
|
||||
this.writer.WriteHeader(statusCode)
|
||||
_, _ = this.writer.WriteString(serverconfigs.DefaultPlanExpireNoticePageBody)
|
||||
}
|
||||
@@ -39,8 +39,8 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
origin := this.reverseProxy.NextOrigin(requestCall)
|
||||
requestCall.CallResponseCallbacks(this.writer)
|
||||
if origin == nil {
|
||||
err := errors.New(this.requestPath() + ": no available backends for reverse proxy")
|
||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
err := errors.New(this.requestFullURL() + ": no available origin sites for reverse proxy")
|
||||
remotelogs.ServerError(this.Server.Id, "HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
|
||||
// 处理Scheme
|
||||
if origin.Addr == nil {
|
||||
err := errors.New(this.requestPath() + ": origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
|
||||
err := errors.New(this.requestFullURL() + ": origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
|
||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
@@ -145,7 +145,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
}
|
||||
|
||||
// 获取请求客户端
|
||||
client, err := SharedHTTPClientPool.Client(this.RawReq, origin, originAddr)
|
||||
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
@@ -249,10 +249,12 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
shouldAutoFlush := this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
|
||||
|
||||
// 准备
|
||||
this.writer.Prepare(resp.ContentLength, resp.StatusCode)
|
||||
delayHeaders := this.writer.Prepare(resp.ContentLength, resp.StatusCode)
|
||||
|
||||
// 设置响应代码
|
||||
this.writer.WriteHeader(resp.StatusCode)
|
||||
if !delayHeaders {
|
||||
this.writer.WriteHeader(resp.StatusCode)
|
||||
}
|
||||
|
||||
// 输出到客户端
|
||||
pool := this.bytePool(resp.ContentLength)
|
||||
@@ -279,7 +281,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
|
||||
closeErr := resp.Body.Close()
|
||||
if closeErr != nil {
|
||||
if !this.canIgnore(err) {
|
||||
if !this.canIgnore(closeErr) {
|
||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", closeErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
24
internal/nodes/http_request_traffic_limit.go
Normal file
24
internal/nodes/http_request_traffic_limit.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
)
|
||||
|
||||
// 流量限制
|
||||
func (this *HTTPRequest) doTrafficLimit() {
|
||||
var config = this.Server.TrafficLimit
|
||||
|
||||
this.tags = append(this.tags, "bandwidth")
|
||||
|
||||
var statusCode = 509
|
||||
this.processResponseHeaders(statusCode)
|
||||
|
||||
this.writer.WriteHeader(statusCode)
|
||||
if len(config.NoticePageBody) != 0 {
|
||||
_, _ = this.writer.WriteString(config.NoticePageBody)
|
||||
} else {
|
||||
_, _ = this.writer.WriteString(serverconfigs.DefaultTrafficLimitNoticePageBody)
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,32 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
|
||||
// 当前连接是否已关闭
|
||||
var conn = this.RawReq.Context().Value(HTTPConnContextKey)
|
||||
if conn != nil {
|
||||
trafficConn, ok := conn.(*TrafficConn)
|
||||
if ok && trafficConn.IsClosed() {
|
||||
if isClientConnClosed(conn.(net.Conn)) {
|
||||
this.disableLog = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 是否在全局名单中
|
||||
var remoteAddr = this.requestRemoteAddr(true)
|
||||
if !iplibrary.AllowIP(remoteAddr, this.Server.Id) {
|
||||
this.disableLog = true
|
||||
if conn != nil {
|
||||
_ = conn.(net.Conn).Close()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否在临时黑名单中
|
||||
if waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeService, this.Server.Id, remoteAddr) || waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteAddr) {
|
||||
this.disableLog = true
|
||||
if conn != nil {
|
||||
_ = conn.(net.Conn).Close()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 当前服务的独立设置
|
||||
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
|
||||
blocked, breakChecking := this.checkWAFRequest(this.web.FirewallPolicy)
|
||||
|
||||
@@ -56,7 +56,7 @@ func (this *HTTPRequest) doWebsocket() {
|
||||
}
|
||||
|
||||
clientConn, _, err := this.writer.Hijack()
|
||||
if err != nil {
|
||||
if err != nil || clientConn == nil {
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,17 +3,22 @@ package nodes
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/chai2010/webp"
|
||||
"github.com/andybalholm/brotli"
|
||||
_ "github.com/biessek/golang-ico"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gowebp"
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/webp"
|
||||
"image"
|
||||
"image/gif"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
@@ -39,9 +44,11 @@ type HTTPWriter struct {
|
||||
|
||||
size int64
|
||||
|
||||
webpIsEncoding bool
|
||||
webpBuffer *bytes.Buffer
|
||||
webpIsWriting bool
|
||||
webpIsEncoding bool
|
||||
webpBuffer *bytes.Buffer
|
||||
webpIsWriting bool
|
||||
webpOriginContentType string
|
||||
webpOriginEncoding string // gzip
|
||||
|
||||
compressionConfig *serverconfigs.HTTPCompressionConfig
|
||||
compressionWriter compressions.Writer
|
||||
@@ -92,12 +99,16 @@ func (this *HTTPWriter) SetCompression(config *serverconfigs.HTTPCompressionConf
|
||||
|
||||
// Prepare 准备输出
|
||||
// 缓存不调用此函数
|
||||
func (this *HTTPWriter) Prepare(size int64, status int) {
|
||||
func (this *HTTPWriter) Prepare(size int64, status int) (delayHeaders bool) {
|
||||
this.size = size
|
||||
this.statusCode = status
|
||||
|
||||
if status == http.StatusOK {
|
||||
this.prepareWebP(size)
|
||||
|
||||
if this.webpIsEncoding {
|
||||
delayHeaders = true
|
||||
}
|
||||
}
|
||||
|
||||
this.prepareCache(size)
|
||||
@@ -106,6 +117,8 @@ func (this *HTTPWriter) Prepare(size int64, status int) {
|
||||
if !this.webpIsEncoding {
|
||||
this.PrepareCompression(size)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Raw 包装前的原始的Writer
|
||||
@@ -261,11 +274,61 @@ func (this *HTTPWriter) Close() {
|
||||
// webp writer
|
||||
if this.isOk && this.webpIsEncoding {
|
||||
var bufferLen = int64(this.webpBuffer.Len())
|
||||
atomic.AddInt64(&webpTotalBufferSize, bufferLen*8)
|
||||
atomic.AddInt64(&webpTotalBufferSize, bufferLen*4)
|
||||
|
||||
imageData, _, err := image.Decode(this.webpBuffer)
|
||||
// 需要把字节读取出来做备份,防止在image.Decode()过程中丢失
|
||||
var imageBytes = this.webpBuffer.Bytes()
|
||||
var imageData image.Image
|
||||
var gifImage *gif.GIF
|
||||
var isGif = strings.Contains(this.webpOriginContentType, "image/gif")
|
||||
|
||||
var err error
|
||||
if this.webpOriginEncoding == "gzip" {
|
||||
this.Header().Del("Content-Encoding")
|
||||
var reader *gzip.Reader
|
||||
reader, err = gzip.NewReader(this.webpBuffer)
|
||||
if err == nil {
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
}()
|
||||
if isGif {
|
||||
gifImage, err = gif.DecodeAll(reader)
|
||||
} else {
|
||||
imageData, _, err = image.Decode(reader)
|
||||
}
|
||||
}
|
||||
} else if this.webpOriginEncoding == "deflate" {
|
||||
this.Header().Del("Content-Encoding")
|
||||
var reader io.ReadCloser
|
||||
reader = flate.NewReader(this.webpBuffer)
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
}()
|
||||
if isGif {
|
||||
gifImage, err = gif.DecodeAll(reader)
|
||||
} else {
|
||||
imageData, _, err = image.Decode(reader)
|
||||
}
|
||||
} else if this.webpOriginEncoding == "br" {
|
||||
this.Header().Del("Content-Encoding")
|
||||
var reader *brotli.Reader
|
||||
reader = brotli.NewReader(this.webpBuffer)
|
||||
if isGif {
|
||||
gifImage, err = gif.DecodeAll(reader)
|
||||
} else {
|
||||
imageData, _, err = image.Decode(reader)
|
||||
}
|
||||
} else {
|
||||
if isGif {
|
||||
gifImage, err = gif.DecodeAll(this.webpBuffer)
|
||||
} else {
|
||||
imageData, _, err = image.Decode(this.webpBuffer)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
_, _ = io.Copy(this.writer, this.webpBuffer)
|
||||
this.Header().Set("Content-Type", this.webpOriginContentType)
|
||||
this.WriteHeader(http.StatusOK)
|
||||
_, _ = this.writer.Write(imageBytes)
|
||||
|
||||
// 处理缓存
|
||||
if this.cacheWriter != nil {
|
||||
@@ -279,16 +342,47 @@ func (this *HTTPWriter) Close() {
|
||||
}
|
||||
this.webpIsWriting = true
|
||||
|
||||
err = webp.Encode(this, imageData, &webp.Options{
|
||||
Lossless: false,
|
||||
Quality: f,
|
||||
Exact: true,
|
||||
})
|
||||
if imageData != nil {
|
||||
err = gowebp.Encode(this, imageData, &gowebp.Options{
|
||||
Lossless: false,
|
||||
Quality: f,
|
||||
Exact: true,
|
||||
})
|
||||
} else if gifImage != nil {
|
||||
anim := gowebp.NewWebpAnimation(gifImage.Config.Width, gifImage.Config.Height, gifImage.LoopCount)
|
||||
anim.WebPAnimEncoderOptions.SetKmin(9)
|
||||
anim.WebPAnimEncoderOptions.SetKmax(17)
|
||||
defer anim.ReleaseMemory()
|
||||
webpConfig := gowebp.NewWebpConfig()
|
||||
//webpConfig.SetLossless(1)
|
||||
webpConfig.SetQuality(f)
|
||||
|
||||
timeline := 0
|
||||
|
||||
for i, img := range gifImage.Image {
|
||||
err = anim.AddFrame(img, timeline, webpConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
timeline += gifImage.Delay[i] * 10
|
||||
}
|
||||
if err == nil {
|
||||
err = anim.AddFrame(nil, timeline, webpConfig)
|
||||
|
||||
if err == nil {
|
||||
err = anim.Encode(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if !this.req.canIgnore(err) {
|
||||
remotelogs.Error("HTTP_WRITER", "encode webp failed: "+err.Error())
|
||||
}
|
||||
|
||||
this.Header().Set("Content-Type", this.webpOriginContentType)
|
||||
this.WriteHeader(http.StatusOK)
|
||||
_, _ = this.writer.Write(imageBytes)
|
||||
|
||||
// 处理缓存
|
||||
if this.cacheWriter != nil {
|
||||
_ = this.cacheWriter.Discard()
|
||||
@@ -297,7 +391,7 @@ func (this *HTTPWriter) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
atomic.AddInt64(&webpTotalBufferSize, -bufferLen*8)
|
||||
atomic.AddInt64(&webpTotalBufferSize, -bufferLen*4)
|
||||
this.webpBuffer.Reset()
|
||||
}
|
||||
|
||||
@@ -368,9 +462,19 @@ func (this *HTTPWriter) prepareWebP(size int64) {
|
||||
this.req.web.WebP.IsOn &&
|
||||
this.req.web.WebP.MatchResponse(this.Header().Get("Content-Type"), size, filepath.Ext(this.req.requestPath()), this.req.Format) &&
|
||||
this.req.web.WebP.MatchAccept(this.req.requestHeader("Accept")) &&
|
||||
len(this.writer.Header().Get("Content-Encoding")) == 0 &&
|
||||
atomic.LoadInt64(&webpTotalBufferSize) < webpMaxBufferSize {
|
||||
|
||||
var contentEncoding = this.writer.Header().Get("Content-Encoding")
|
||||
switch contentEncoding {
|
||||
case "gzip", "deflate", "br":
|
||||
this.webpOriginEncoding = contentEncoding
|
||||
case "": // 空
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
this.webpIsEncoding = true
|
||||
this.webpOriginContentType = this.Header().Get("Content-Type")
|
||||
this.webpBuffer = webpBufferPool.Get()
|
||||
|
||||
this.Header().Del("Content-Length")
|
||||
@@ -387,7 +491,8 @@ func (this *HTTPWriter) PrepareCompression(size int64) {
|
||||
}
|
||||
|
||||
// 如果已经有编码则不处理
|
||||
if len(this.writer.Header().Get("Content-Encoding")) > 0 {
|
||||
var contentEncoding = this.writer.Header().Get("Content-Encoding")
|
||||
if len(contentEncoding) > 0 && (!this.compressionConfig.DecompressData || !lists.ContainsString([]string{"gzip", "deflate", "br"}, contentEncoding)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -401,6 +506,12 @@ func (this *HTTPWriter) PrepareCompression(size int64) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 压缩前后如果编码一致,则不处理
|
||||
if compressionEncoding == contentEncoding {
|
||||
return
|
||||
}
|
||||
|
||||
this.compressionType = compressionType
|
||||
|
||||
// compression writer
|
||||
@@ -411,6 +522,15 @@ func (this *HTTPWriter) PrepareCompression(size int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// convert between encodings
|
||||
if len(contentEncoding) > 0 {
|
||||
this.compressionWriter, err = compressions.NewEncodingWriter(contentEncoding, this.compressionWriter)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_WRITER", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// body copy
|
||||
if this.bodyCopying {
|
||||
this.compressionBodyBuffer = bytes.NewBuffer([]byte{})
|
||||
@@ -432,11 +552,6 @@ func (this *HTTPWriter) prepareCache(size int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// 不支持Range
|
||||
if len(this.Header().Get("Content-Range")) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cachePolicy := this.req.Server.HTTPCachePolicy
|
||||
if cachePolicy == nil || !cachePolicy.IsOn {
|
||||
return
|
||||
@@ -447,17 +562,36 @@ func (this *HTTPWriter) prepareCache(size int64) {
|
||||
return
|
||||
}
|
||||
|
||||
var addStatusHeader = this.req.web != nil && this.req.web.Cache != nil && this.req.web.Cache.AddStatusHeader
|
||||
|
||||
// 不支持Range
|
||||
if len(this.Header().Get("Content-Range")) > 0 {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, not supported Content-Range")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 如果允许 ChunkedEncoding,就无需尺寸的判断,因为此时的 size 为 -1
|
||||
if !cacheRef.AllowChunkedEncoding && size < 0 {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, ChunkedEncoding")
|
||||
}
|
||||
return
|
||||
}
|
||||
if size >= 0 && ((cacheRef.MaxSizeBytes() > 0 && size > cacheRef.MaxSizeBytes()) ||
|
||||
(cachePolicy.MaxSizeBytes() > 0 && size > cachePolicy.MaxSizeBytes()) || (cacheRef.MinSizeBytes() > size)) {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, Content-Length")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查状态
|
||||
if len(cacheRef.Status) > 0 && !lists.ContainsInt(cacheRef.Status, this.StatusCode()) {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, Status: "+types.String(this.StatusCode()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -468,6 +602,9 @@ func (this *HTTPWriter) prepareCache(size int64) {
|
||||
values := strings.Split(cacheControl, ",")
|
||||
for _, value := range values {
|
||||
if cacheRef.ContainsCacheControl(strings.TrimSpace(value)) {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, Cache-Control: "+cacheControl)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -476,19 +613,29 @@ func (this *HTTPWriter) prepareCache(size int64) {
|
||||
|
||||
// Set-Cookie
|
||||
if cacheRef.SkipResponseSetCookie && len(this.writer.Header().Get("Set-Cookie")) > 0 {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, Set-Cookie")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 校验其他条件
|
||||
if cacheRef.Conds != nil && cacheRef.Conds.HasResponseConds() && !cacheRef.Conds.MatchResponse(this.req.Format) {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, ResponseConds")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 打开缓存写入
|
||||
storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
|
||||
if storage == nil {
|
||||
if addStatusHeader {
|
||||
this.Header().Set("X-Cache", "BYPASS, Storage")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.cacheStorage = storage
|
||||
life := cacheRef.LifeSeconds()
|
||||
if life <= 60 { // 最小不能少于1分钟
|
||||
|
||||
@@ -59,7 +59,7 @@ func (this *Listener) listenTCP() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netListener = NewTrafficListener(netListener)
|
||||
netListener = NewClientListener(netListener, protocol.IsHTTPFamily() || protocol.IsHTTPSFamily())
|
||||
events.On(events.EventQuit, func() {
|
||||
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
|
||||
_ = netListener.Close()
|
||||
|
||||
@@ -5,35 +5,28 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"golang.org/x/net/http2"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type BaseListener struct {
|
||||
serversLocker sync.RWMutex
|
||||
namedServersLocker sync.RWMutex
|
||||
namedServers map[string]*NamedServer // 域名 => server
|
||||
|
||||
Group *serverconfigs.ServerAddressGroup
|
||||
|
||||
countActiveConnections int64 // 当前活跃的连接数
|
||||
}
|
||||
|
||||
// 初始化
|
||||
// Init 初始化
|
||||
func (this *BaseListener) Init() {
|
||||
this.namedServers = map[string]*NamedServer{}
|
||||
}
|
||||
|
||||
// 清除既有配置
|
||||
// Reset 清除既有配置
|
||||
func (this *BaseListener) Reset() {
|
||||
this.namedServersLocker.Lock()
|
||||
this.namedServers = map[string]*NamedServer{}
|
||||
this.namedServersLocker.Unlock()
|
||||
|
||||
}
|
||||
|
||||
// 获取当前活跃连接数
|
||||
// CountActiveListeners 获取当前活跃连接数
|
||||
func (this *BaseListener) CountActiveListeners() int {
|
||||
return types.Int(this.countActiveConnections)
|
||||
}
|
||||
@@ -67,7 +60,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
return nil, err
|
||||
}
|
||||
if cert == nil {
|
||||
return nil, errors.New("[proxy]no certs found for '" + info.ServerName + "'")
|
||||
return nil, errors.New("no ssl certs found for '" + info.ServerName + "'")
|
||||
}
|
||||
return cert, nil
|
||||
},
|
||||
@@ -83,7 +76,7 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
return nil, err
|
||||
}
|
||||
if cert == nil {
|
||||
return nil, errors.New("[proxy]no certs found for '" + info.ServerName + "'")
|
||||
return nil, errors.New("no ssl certs found for '" + info.ServerName + "'")
|
||||
}
|
||||
return cert, nil
|
||||
},
|
||||
@@ -92,9 +85,6 @@ func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||
|
||||
// 根据域名匹配证书
|
||||
func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.Certificate, error) {
|
||||
this.serversLocker.RLock()
|
||||
defer this.serversLocker.RUnlock()
|
||||
|
||||
group := this.Group
|
||||
|
||||
if group == nil {
|
||||
@@ -108,9 +98,9 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
|
||||
return nil, nil, errors.New("no tls server name matched")
|
||||
}
|
||||
|
||||
firstServer := group.FirstServer()
|
||||
firstServer := group.FirstTLSServer()
|
||||
if firstServer == nil {
|
||||
return nil, nil, errors.New("no server available")
|
||||
return nil, nil, errors.New("no tls server available")
|
||||
}
|
||||
sslConfig := firstServer.SSLPolicy()
|
||||
|
||||
@@ -119,14 +109,14 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
|
||||
|
||||
}
|
||||
return nil, nil, errors.New("no tls server name found")
|
||||
|
||||
}
|
||||
|
||||
// 通过代理服务域名配置匹配
|
||||
server, _ := this.findNamedServer(domain)
|
||||
if server == nil || server.SSLPolicy() == nil || !server.SSLPolicy().IsOn {
|
||||
// 搜索所有的Server,通过SSL证书内容中的DNSName匹配
|
||||
for _, server := range group.Servers {
|
||||
// 找不到或者此时的服务没有配置证书,需要搜索所有的Server,通过SSL证书内容中的DNSName匹配
|
||||
// TODO 需要思考这种情况下是否允许访问
|
||||
for _, server := range group.Servers() {
|
||||
if server.SSLPolicy() == nil || !server.SSLPolicy().IsOn {
|
||||
continue
|
||||
}
|
||||
@@ -136,7 +126,7 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, errors.New("[proxy]no server found for '" + domain + "'")
|
||||
return nil, nil, errors.New("no server found for '" + domain + "'")
|
||||
}
|
||||
|
||||
// 证书是否匹配
|
||||
@@ -146,6 +136,10 @@ func (this *BaseListener) matchSSL(domain string) (*sslconfigs.SSLPolicy, *tls.C
|
||||
return sslConfig, cert, nil
|
||||
}
|
||||
|
||||
if len(sslConfig.Certs) == 0 {
|
||||
remotelogs.ServerError(server.Id, "BASE_LISTENER", "no ssl certs found for '"+domain+"', server id: "+types.String(server.Id))
|
||||
}
|
||||
|
||||
return sslConfig, sslConfig.FirstCert(), nil
|
||||
}
|
||||
|
||||
@@ -173,11 +167,8 @@ func (this *BaseListener) findNamedServer(name string) (serverConfig *serverconf
|
||||
}
|
||||
|
||||
// 如果没有找到,则匹配到第一个
|
||||
this.serversLocker.RLock()
|
||||
defer this.serversLocker.RUnlock()
|
||||
|
||||
group := this.Group
|
||||
currentServers := group.Servers
|
||||
currentServers := group.Servers()
|
||||
countServers := len(currentServers)
|
||||
if countServers == 0 {
|
||||
return nil, ""
|
||||
@@ -192,64 +183,40 @@ func (this *BaseListener) findNamedServerMatched(name string) (serverConfig *ser
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// 读取缓存
|
||||
this.namedServersLocker.RLock()
|
||||
namedServer, found := this.namedServers[name]
|
||||
if found {
|
||||
this.namedServersLocker.RUnlock()
|
||||
return namedServer.Server, namedServer.Name
|
||||
server := group.MatchServerName(name)
|
||||
if server != nil {
|
||||
return server, name
|
||||
}
|
||||
this.namedServersLocker.RUnlock()
|
||||
|
||||
this.serversLocker.RLock()
|
||||
defer this.serversLocker.RUnlock()
|
||||
|
||||
currentServers := group.Servers
|
||||
countServers := len(currentServers)
|
||||
if countServers == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// 只记录N个记录,防止内存耗尽
|
||||
maxNamedServers := 100_0000
|
||||
|
||||
// 是否严格匹配域名
|
||||
matchDomainStrictly := sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly
|
||||
|
||||
// 如果只有一个server,则默认为这个
|
||||
var currentServers = group.Servers()
|
||||
var countServers = len(currentServers)
|
||||
if countServers == 1 && !matchDomainStrictly {
|
||||
return currentServers[0], name
|
||||
}
|
||||
|
||||
// 精确查找
|
||||
for _, server := range currentServers {
|
||||
if server.MatchNameStrictly(name) {
|
||||
this.namedServersLocker.Lock()
|
||||
if len(this.namedServers) < maxNamedServers {
|
||||
this.namedServers[name] = &NamedServer{
|
||||
Name: name,
|
||||
Server: server,
|
||||
}
|
||||
}
|
||||
this.namedServersLocker.Unlock()
|
||||
return server, name
|
||||
}
|
||||
}
|
||||
|
||||
// 模糊查找
|
||||
for _, server := range currentServers {
|
||||
if matched := server.MatchName(name); matched {
|
||||
this.namedServersLocker.Lock()
|
||||
if len(this.namedServers) < maxNamedServers {
|
||||
this.namedServers[name] = &NamedServer{
|
||||
Name: name,
|
||||
Server: server,
|
||||
}
|
||||
}
|
||||
this.namedServersLocker.Unlock()
|
||||
return server, name
|
||||
}
|
||||
}
|
||||
|
||||
return nil, name
|
||||
}
|
||||
|
||||
// 使用CNAME来查找服务
|
||||
// TODO 防止单IP随机生成域名攻击
|
||||
func (this *BaseListener) findServerWithCNAME(domain string) *serverconfigs.ServerConfig {
|
||||
if !sharedNodeConfig.SupportCNAME {
|
||||
return nil
|
||||
}
|
||||
|
||||
var realName = sharedCNAMEManager.Lookup(domain)
|
||||
if len(realName) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
group := this.Group
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return group.MatchServerCNAME(realName)
|
||||
}
|
||||
|
||||
36
internal/nodes/listener_base_test.go
Normal file
36
internal/nodes/listener_base_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBaseListener_FindServer(t *testing.T) {
|
||||
sharedNodeConfig = &nodeconfigs.NodeConfig{}
|
||||
|
||||
var listener = &BaseListener{namedServers: map[string]*NamedServer{}}
|
||||
listener.Group = &serverconfigs.ServerAddressGroup{}
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var server = &serverconfigs.ServerConfig{
|
||||
IsOn: true,
|
||||
Name: types.String(i) + ".hello.com",
|
||||
ServerNames: []*serverconfigs.ServerNameConfig{
|
||||
{Name: types.String(i) + ".hello.com"},
|
||||
},
|
||||
}
|
||||
_ = server.Init()
|
||||
listener.Group.Servers = append(listener.Group.Servers, server)
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
t.Log(listener.findNamedServerMatched("855555.hello.com"))
|
||||
}
|
||||
@@ -37,18 +37,13 @@ type HTTPListener struct {
|
||||
}
|
||||
|
||||
func (this *HTTPListener) Serve() error {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
this.handleHTTP(writer, request)
|
||||
})
|
||||
|
||||
this.addr = this.Group.Addr()
|
||||
this.isHTTP = this.Group.IsHTTP()
|
||||
this.isHTTPS = this.Group.IsHTTPS()
|
||||
|
||||
this.httpServer = &http.Server{
|
||||
Addr: this.addr,
|
||||
Handler: handler,
|
||||
Handler: this,
|
||||
ReadHeaderTimeout: 2 * time.Second, // TODO 改成可以配置
|
||||
IdleTimeout: 2 * time.Minute, // TODO 改成可以配置
|
||||
ErrorLog: httpErrorLogger,
|
||||
@@ -118,8 +113,8 @@ func (this *HTTPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
||||
this.Reset()
|
||||
}
|
||||
|
||||
// 处理HTTP请求
|
||||
func (this *HTTPListener) handleHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
|
||||
// ServerHTTP 处理HTTP请求
|
||||
func (this *HTTPListener) ServeHTTP(rawWriter http.ResponseWriter, rawReq *http.Request) {
|
||||
// 域名
|
||||
reqHost := rawReq.Host
|
||||
|
||||
@@ -157,33 +152,38 @@ func (this *HTTPListener) handleHTTP(rawWriter http.ResponseWriter, rawReq *http
|
||||
|
||||
server, serverName := this.findNamedServer(domain)
|
||||
if server == nil {
|
||||
// 严格匹配域名模式下,我们拒绝用户访问
|
||||
if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly {
|
||||
httpAllConfig := sharedNodeConfig.GlobalConfig.HTTPAll
|
||||
mismatchAction := httpAllConfig.DomainMismatchAction
|
||||
if mismatchAction != nil && mismatchAction.Code == "page" {
|
||||
if mismatchAction.Options != nil {
|
||||
rawWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rawWriter.WriteHeader(mismatchAction.Options.GetInt("statusCode"))
|
||||
_, _ = rawWriter.Write([]byte(mismatchAction.Options.GetString("contentHTML")))
|
||||
server = this.findServerWithCNAME(domain)
|
||||
if server == nil {
|
||||
// 严格匹配域名模式下,我们拒绝用户访问
|
||||
if sharedNodeConfig.GlobalConfig != nil && sharedNodeConfig.GlobalConfig.HTTPAll.MatchDomainStrictly {
|
||||
httpAllConfig := sharedNodeConfig.GlobalConfig.HTTPAll
|
||||
mismatchAction := httpAllConfig.DomainMismatchAction
|
||||
if mismatchAction != nil && mismatchAction.Code == "page" {
|
||||
if mismatchAction.Options != nil {
|
||||
rawWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rawWriter.WriteHeader(mismatchAction.Options.GetInt("statusCode"))
|
||||
_, _ = rawWriter.Write([]byte(mismatchAction.Options.GetString("contentHTML")))
|
||||
} else {
|
||||
http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
hijacker, ok := rawWriter.(http.Hijacker)
|
||||
if ok {
|
||||
conn, _, _ := hijacker.Hijack()
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
return
|
||||
hijacker, ok := rawWriter.(http.Hijacker)
|
||||
if ok {
|
||||
conn, _, _ := hijacker.Hijack()
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
|
||||
return
|
||||
http.Error(rawWriter, "404 page not found: '"+rawReq.URL.String()+"'", http.StatusNotFound)
|
||||
return
|
||||
} else {
|
||||
serverName = domain
|
||||
}
|
||||
}
|
||||
|
||||
// 包装新请求对象
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
@@ -63,17 +65,17 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
var serverName = tlsConn.ConnectionState().ServerName
|
||||
if len(serverName) > 0 {
|
||||
// 统计
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, serverName, 0, 0, 1, 0, 0, 0)
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, serverName, 0, 0, 1, 0, 0, 0, firstServer.ShouldCheckTrafficLimit(), firstServer.PlanId())
|
||||
recordStat = true
|
||||
}
|
||||
}
|
||||
|
||||
// 统计
|
||||
if !recordStat {
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, "", 0, 0, 1, 0, 0, 0)
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, "", 0, 0, 1, 0, 0, 0, firstServer.ShouldCheckTrafficLimit(), firstServer.PlanId())
|
||||
}
|
||||
|
||||
originConn, err := this.connectOrigin(firstServer.ReverseProxy, conn.RemoteAddr().String())
|
||||
originConn, err := this.connectOrigin(firstServer.Id, firstServer.ReverseProxy, conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,6 +85,31 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
_ = originConn.Close()
|
||||
}
|
||||
|
||||
// PROXY Protocol
|
||||
if firstServer.ReverseProxy != nil &&
|
||||
firstServer.ReverseProxy.ProxyProtocol != nil &&
|
||||
firstServer.ReverseProxy.ProxyProtocol.IsOn &&
|
||||
(firstServer.ReverseProxy.ProxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || firstServer.ReverseProxy.ProxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
var remoteAddr = conn.RemoteAddr()
|
||||
var transportProtocol = proxyproto.TCPv4
|
||||
if strings.Contains(remoteAddr.String(), "[") {
|
||||
transportProtocol = proxyproto.TCPv6
|
||||
}
|
||||
header := proxyproto.Header{
|
||||
Version: byte(firstServer.ReverseProxy.ProxyProtocol.Version),
|
||||
Command: proxyproto.PROXY,
|
||||
TransportProtocol: transportProtocol,
|
||||
SourceAddr: remoteAddr,
|
||||
DestinationAddr: conn.LocalAddr(),
|
||||
}
|
||||
_, err = header.WriteTo(originConn)
|
||||
if err != nil {
|
||||
closer()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 从源站读取
|
||||
go func() {
|
||||
originBuffer := bytePool32k.Get()
|
||||
defer func() {
|
||||
@@ -98,7 +125,9 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
}
|
||||
|
||||
// 记录流量
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, "", int64(n), 0, 0, 0, 0, 0)
|
||||
if firstServer != nil {
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, "", int64(n), 0, 0, 0, 0, 0, firstServer.ShouldCheckTrafficLimit(), firstServer.PlanId())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
closer()
|
||||
@@ -107,6 +136,7 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
}
|
||||
}()
|
||||
|
||||
// 从客户端读取
|
||||
clientBuffer := bytePool32k.Get()
|
||||
defer func() {
|
||||
bytePool32k.Put(clientBuffer)
|
||||
@@ -134,7 +164,7 @@ func (this *TCPListener) Close() error {
|
||||
return this.Listener.Close()
|
||||
}
|
||||
|
||||
func (this *TCPListener) connectOrigin(reverseProxy *serverconfigs.ReverseProxyConfig, remoteAddr string) (conn net.Conn, err error) {
|
||||
func (this *TCPListener) connectOrigin(serverId int64, reverseProxy *serverconfigs.ReverseProxyConfig, remoteAddr string) (conn net.Conn, err error) {
|
||||
if reverseProxy == nil {
|
||||
return nil, errors.New("no reverse proxy config")
|
||||
}
|
||||
@@ -147,7 +177,7 @@ func (this *TCPListener) connectOrigin(reverseProxy *serverconfigs.ReverseProxyC
|
||||
}
|
||||
conn, err = OriginConnect(origin, remoteAddr)
|
||||
if err != nil {
|
||||
remotelogs.Error("TCP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error())
|
||||
remotelogs.ServerError(serverId, "TCP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error())
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
func TestListener_Listen(t *testing.T) {
|
||||
listener := NewListener()
|
||||
|
||||
group := serverconfigs.NewServerGroup("http://:1234")
|
||||
group := serverconfigs.NewServerAddressGroup("https://:1234")
|
||||
|
||||
listener.Reload(group)
|
||||
err := listener.Listen()
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -19,6 +21,8 @@ type UDPListener struct {
|
||||
connMap map[string]*UDPConn
|
||||
connLocker sync.Mutex
|
||||
connTicker *utils.Ticker
|
||||
|
||||
reverseProxy *serverconfigs.ReverseProxyConfig
|
||||
}
|
||||
|
||||
func (this *UDPListener) Serve() error {
|
||||
@@ -26,8 +30,9 @@ func (this *UDPListener) Serve() error {
|
||||
if firstServer == nil {
|
||||
return errors.New("no server available")
|
||||
}
|
||||
if firstServer.ReverseProxy == nil {
|
||||
return errors.New("no ReverseProxy configured for the server")
|
||||
this.reverseProxy = firstServer.ReverseProxy
|
||||
if this.reverseProxy == nil {
|
||||
return errors.New("no ReverseProxy configured for the server '" + firstServer.Name + "'")
|
||||
}
|
||||
|
||||
this.connMap = map[string]*UDPConn{}
|
||||
@@ -50,7 +55,7 @@ func (this *UDPListener) Serve() error {
|
||||
ok = false
|
||||
}
|
||||
if !ok {
|
||||
originConn, err := this.connectOrigin(firstServer.ReverseProxy, "")
|
||||
originConn, err := this.connectOrigin(firstServer.Id, this.reverseProxy, addr)
|
||||
if err != nil {
|
||||
remotelogs.Error("UDP_LISTENER", "unable to connect to origin server: "+err.Error())
|
||||
continue
|
||||
@@ -59,7 +64,7 @@ func (this *UDPListener) Serve() error {
|
||||
remotelogs.Error("UDP_LISTENER", "unable to find a origin server")
|
||||
continue
|
||||
}
|
||||
conn = NewUDPConn(firstServer.Id, addr, this.Listener, originConn.(*net.UDPConn))
|
||||
conn = NewUDPConn(firstServer, addr, this.Listener, originConn.(*net.UDPConn))
|
||||
this.connLocker.Lock()
|
||||
this.connMap[addr.String()] = conn
|
||||
this.connLocker.Unlock()
|
||||
@@ -87,9 +92,16 @@ func (this *UDPListener) Close() error {
|
||||
func (this *UDPListener) Reload(group *serverconfigs.ServerAddressGroup) {
|
||||
this.Group = group
|
||||
this.Reset()
|
||||
|
||||
// 重置配置
|
||||
firstServer := this.Group.FirstServer()
|
||||
if firstServer == nil {
|
||||
return
|
||||
}
|
||||
this.reverseProxy = firstServer.ReverseProxy
|
||||
}
|
||||
|
||||
func (this *UDPListener) connectOrigin(reverseProxy *serverconfigs.ReverseProxyConfig, remoteAddr string) (conn net.Conn, err error) {
|
||||
func (this *UDPListener) connectOrigin(serverId int64, reverseProxy *serverconfigs.ReverseProxyConfig, remoteAddr net.Addr) (conn net.Conn, err error) {
|
||||
if reverseProxy == nil {
|
||||
return nil, errors.New("no reverse proxy config")
|
||||
}
|
||||
@@ -100,11 +112,34 @@ func (this *UDPListener) connectOrigin(reverseProxy *serverconfigs.ReverseProxyC
|
||||
if origin == nil {
|
||||
continue
|
||||
}
|
||||
conn, err = OriginConnect(origin, remoteAddr)
|
||||
conn, err = OriginConnect(origin, remoteAddr.String())
|
||||
if err != nil {
|
||||
remotelogs.Error("UDP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error())
|
||||
remotelogs.ServerError(serverId, "UDP_LISTENER", "unable to connect origin: "+origin.Addr.Host+":"+origin.Addr.PortRange+": "+err.Error())
|
||||
continue
|
||||
} else {
|
||||
// PROXY Protocol
|
||||
if reverseProxy != nil &&
|
||||
reverseProxy.ProxyProtocol != nil &&
|
||||
reverseProxy.ProxyProtocol.IsOn &&
|
||||
(reverseProxy.ProxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || reverseProxy.ProxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
|
||||
var transportProtocol = proxyproto.UDPv4
|
||||
if strings.Contains(remoteAddr.String(), "[") {
|
||||
transportProtocol = proxyproto.UDPv6
|
||||
}
|
||||
header := proxyproto.Header{
|
||||
Version: byte(reverseProxy.ProxyProtocol.Version),
|
||||
Command: proxyproto.PROXY,
|
||||
TransportProtocol: transportProtocol,
|
||||
SourceAddr: remoteAddr,
|
||||
DestinationAddr: this.Listener.LocalAddr(),
|
||||
}
|
||||
_, err = header.WriteTo(conn)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -139,7 +174,7 @@ type UDPConn struct {
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewUDPConn(serverId int64, addr net.Addr, proxyConn *net.UDPConn, serverConn *net.UDPConn) *UDPConn {
|
||||
func NewUDPConn(server *serverconfigs.ServerConfig, addr net.Addr, proxyConn *net.UDPConn, serverConn *net.UDPConn) *UDPConn {
|
||||
conn := &UDPConn{
|
||||
addr: addr,
|
||||
proxyConn: proxyConn,
|
||||
@@ -149,7 +184,9 @@ func NewUDPConn(serverId int64, addr net.Addr, proxyConn *net.UDPConn, serverCon
|
||||
}
|
||||
|
||||
// 统计
|
||||
stats.SharedTrafficStatManager.Add(serverId, "", 0, 0, 1, 0, 0, 0)
|
||||
if server != nil {
|
||||
stats.SharedTrafficStatManager.Add(server.Id, "", 0, 0, 1, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
}
|
||||
|
||||
go func() {
|
||||
buffer := bytePool32k.Get()
|
||||
@@ -168,7 +205,9 @@ func NewUDPConn(serverId int64, addr net.Addr, proxyConn *net.UDPConn, serverCon
|
||||
}
|
||||
|
||||
// 记录流量
|
||||
stats.SharedTrafficStatManager.Add(serverId, "", int64(n), 0, 0, 0, 0, 0)
|
||||
if server != nil {
|
||||
stats.SharedTrafficStatManager.Add(server.Id, "", int64(n), 0, 0, 0, 0, 0, server.ShouldCheckTrafficLimit(), server.PlanId())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
conn.isOk = false
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package nodes
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
|
||||
// 域名和服务映射
|
||||
type NamedServer struct {
|
||||
Name string // 匹配后的域名
|
||||
Server *serverconfigs.ServerConfig // 匹配后的服务配置
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
@@ -15,7 +16,9 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
@@ -75,6 +78,9 @@ func (this *Node) Start() {
|
||||
DaemonPid = os.Getppid()
|
||||
}
|
||||
|
||||
// 处理异常
|
||||
this.handlePanic()
|
||||
|
||||
// 启动事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
@@ -86,14 +92,14 @@ func (this *Node) Start() {
|
||||
}
|
||||
|
||||
// 读取API配置
|
||||
err = this.syncConfig()
|
||||
err = this.syncConfig(0)
|
||||
if err != nil {
|
||||
_, err := nodeconfigs.SharedNodeConfig()
|
||||
if err != nil {
|
||||
// 无本地数据时,会尝试多次读取
|
||||
tryTimes := 0
|
||||
for {
|
||||
err := this.syncConfig()
|
||||
err := this.syncConfig(0)
|
||||
if err != nil {
|
||||
tryTimes++
|
||||
|
||||
@@ -125,6 +131,7 @@ func (this *Node) Start() {
|
||||
remotelogs.Error("NODE", "start failed: read node config failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
err = nodeConfig.Init()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "init node config failed: "+err.Error())
|
||||
@@ -178,6 +185,7 @@ func (this *Node) Daemon() {
|
||||
|
||||
// 可以标记当前是从守护进程启动的
|
||||
_ = os.Setenv("EdgeDaemon", "on")
|
||||
_ = os.Setenv("EdgeBackground", "on")
|
||||
|
||||
cmd := exec.Command(exe)
|
||||
err = cmd.Start()
|
||||
@@ -225,6 +233,9 @@ func (this *Node) InstallSystemService() error {
|
||||
|
||||
// 循环
|
||||
func (this *Node) loop() error {
|
||||
var tr = trackers.Begin("CHECK_NODE_CONFIG_CHANGES")
|
||||
defer tr.End()
|
||||
|
||||
// 检查api.yaml是否存在
|
||||
apiConfigFile := Tea.ConfigFile("api.yaml")
|
||||
_, err := os.Stat(apiConfigFile)
|
||||
@@ -257,7 +268,11 @@ func (this *Node) loop() error {
|
||||
return err
|
||||
}
|
||||
case "configChanged":
|
||||
err := this.syncConfig()
|
||||
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,
|
||||
@@ -283,7 +298,7 @@ func (this *Node) loop() error {
|
||||
}
|
||||
|
||||
// 读取API配置
|
||||
func (this *Node) syncConfig() error {
|
||||
func (this *Node) syncConfig(taskVersion int64) error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
@@ -314,7 +329,9 @@ func (this *Node) syncConfig() error {
|
||||
|
||||
// TODO 这里考虑只同步版本号有变更的
|
||||
configResp, err := rpcClient.NodeRPC().FindCurrentNodeConfig(nodeCtx, &pb.FindCurrentNodeConfigRequest{
|
||||
Version: -1, // 更新所有版本
|
||||
Version: -1, // 更新所有版本
|
||||
Compress: true,
|
||||
NodeTaskVersion: taskVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("read config from rpc failed: " + err.Error())
|
||||
@@ -322,14 +339,32 @@ func (this *Node) syncConfig() error {
|
||||
if !configResp.IsChanged {
|
||||
return nil
|
||||
}
|
||||
nodeConfigUpdatedAt = time.Now().Unix()
|
||||
|
||||
configJSON := configResp.NodeJSON
|
||||
if configResp.IsCompressed {
|
||||
var reader = brotli.NewReader(bytes.NewReader(configJSON))
|
||||
var configBuf = &bytes.Buffer{}
|
||||
var buf = make([]byte, 32*1024)
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n > 0 {
|
||||
configBuf.Write(buf[:n])
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
configJSON = configBuf.Bytes()
|
||||
}
|
||||
|
||||
nodeConfigUpdatedAt = time.Now().Unix()
|
||||
|
||||
nodeConfig := &nodeconfigs.NodeConfig{}
|
||||
err = json.Unmarshal(configJSON, nodeConfig)
|
||||
if err != nil {
|
||||
return errors.New("decode config failed: " + err.Error())
|
||||
}
|
||||
teaconst.NodeId = nodeConfig.Id
|
||||
|
||||
// 写入到文件中
|
||||
err = nodeConfig.Save()
|
||||
@@ -407,7 +442,7 @@ func (this *Node) startSyncTimer() {
|
||||
continue
|
||||
}
|
||||
case <-nodeConfigChangedNotify:
|
||||
err := this.syncConfig()
|
||||
err := this.syncConfig(0)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "sync config error: "+err.Error())
|
||||
continue
|
||||
@@ -492,6 +527,16 @@ func (this *Node) listenSock() error {
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "info":
|
||||
exePath, _ := os.Executable()
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "info",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
"version": teaconst.Version,
|
||||
"path": exePath,
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
@@ -515,6 +560,12 @@ func (this *Node) listenSock() error {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
case "trackers":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Params: map[string]interface{}{
|
||||
"labels": trackers.SharedManager.Labels(),
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
46
internal/nodes/node_panic.go
Normal file
46
internal/nodes/node_panic.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !arm64
|
||||
// +build !arm64
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// 处理异常
|
||||
func (this *Node) handlePanic() {
|
||||
// 如果是在前台运行就直接返回
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
return
|
||||
}
|
||||
|
||||
var panicFile = Tea.Root + "/logs/panic.log"
|
||||
|
||||
// 分析panic
|
||||
data, err := ioutil.ReadFile(panicFile)
|
||||
if err == nil {
|
||||
var index = bytes.Index(data, []byte("panic:"))
|
||||
if index >= 0 {
|
||||
remotelogs.Error("NODE", "系统错误,请上报给开发者: "+string(data[index:]))
|
||||
}
|
||||
}
|
||||
|
||||
fp, err := os.OpenFile(panicFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0777)
|
||||
if err != nil {
|
||||
logs.Println("NODE", "open 'panic.log' failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
err = syscall.Dup2(int(fp.Fd()), int(os.Stderr.Fd()))
|
||||
if err != nil {
|
||||
logs.Println("NODE", "write to 'panic.log' failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
10
internal/nodes/node_panic_arm64.go
Normal file
10
internal/nodes/node_panic_arm64.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build arm64
|
||||
// +build arm64
|
||||
|
||||
package nodes
|
||||
|
||||
// 处理异常
|
||||
func (this *Node) handlePanic() {
|
||||
return
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
@@ -58,6 +59,9 @@ func (this *NodeStatusExecutor) update() {
|
||||
return
|
||||
}
|
||||
|
||||
var tr = trackers.Begin("UPLOAD_NODE_STATUS")
|
||||
defer tr.End()
|
||||
|
||||
status := &nodeconfigs.NodeStatus{}
|
||||
status.BuildVersion = teaconst.Version
|
||||
status.BuildVersionCode = utils.VersionToLong(teaconst.Version)
|
||||
@@ -68,8 +72,8 @@ func (this *NodeStatusExecutor) update() {
|
||||
status.ConnectionCount = sharedListenerManager.TotalActiveConnections()
|
||||
status.CacheTotalDiskSize = caches.SharedManager.TotalDiskSize()
|
||||
status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize()
|
||||
status.TrafficInBytes = inTrafficBytes
|
||||
status.TrafficOutBytes = outTrafficBytes
|
||||
status.TrafficInBytes = teaconst.InTrafficBytes
|
||||
status.TrafficOutBytes = teaconst.OutTrafficBytes
|
||||
|
||||
// 记录监控数据
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemConnections, maps.Map{
|
||||
@@ -79,11 +83,26 @@ func (this *NodeStatusExecutor) update() {
|
||||
hostname, _ := os.Hostname()
|
||||
status.Hostname = hostname
|
||||
|
||||
var cpuTR = tr.Begin("cpu")
|
||||
this.updateCPU(status)
|
||||
cpuTR.End()
|
||||
|
||||
var memTR = tr.Begin("memory")
|
||||
this.updateMem(status)
|
||||
memTR.End()
|
||||
|
||||
var loadTR = tr.Begin("load")
|
||||
this.updateLoad(status)
|
||||
loadTR.End()
|
||||
|
||||
var diskTR = tr.Begin("disk")
|
||||
this.updateDisk(status)
|
||||
diskTR.End()
|
||||
|
||||
var cacheSpaceTR = tr.Begin("cache space")
|
||||
this.updateCacheSpace(status)
|
||||
cacheSpaceTR.End()
|
||||
|
||||
status.UpdatedAt = time.Now().Unix()
|
||||
|
||||
// 发送数据
|
||||
@@ -101,7 +120,11 @@ func (this *NodeStatusExecutor) update() {
|
||||
StatusJSON: jsonData,
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
|
||||
if rpc.IsConnError(err) {
|
||||
remotelogs.Warn("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
|
||||
} else {
|
||||
remotelogs.Error("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -60,6 +61,9 @@ func (this *OriginStateManager) Loop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tr = trackers.Begin("CHECK_ORIGIN_STATES")
|
||||
defer tr.End()
|
||||
|
||||
var currentStates = []*OriginState{}
|
||||
this.locker.Lock()
|
||||
for originId, state := range this.stateMap {
|
||||
|
||||
48
internal/nodes/server_cname_manager.go
Normal file
48
internal/nodes/server_cname_manager.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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
|
||||
}
|
||||
19
internal/nodes/server_cname_manager_test.go
Normal file
19
internal/nodes/server_cname_manager_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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"))
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
@@ -53,6 +54,9 @@ func (this *SyncAPINodesTask) Start() {
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
var tr = trackers.Begin("SYNC_API_NODES")
|
||||
defer tr.End()
|
||||
|
||||
// 获取所有可用的节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
@@ -64,7 +68,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
newEndpoints := []string{}
|
||||
for _, node := range resp.Nodes {
|
||||
for _, node := range resp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
|
||||
35
internal/nodes/timezone.go
Normal file
35
internal/nodes/timezone.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 管理时区
|
||||
var lastTimeZone = ""
|
||||
|
||||
events.On(events.EventReload, func() {
|
||||
if sharedNodeConfig != nil {
|
||||
var timeZone = sharedNodeConfig.TimeZone
|
||||
if len(timeZone) == 0 {
|
||||
timeZone = "Asia/Shanghai"
|
||||
}
|
||||
|
||||
if lastTimeZone != timeZone {
|
||||
location, err := time.LoadLocation(timeZone)
|
||||
if err != nil {
|
||||
remotelogs.Error("TIMEZONE", "change time zone failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
remotelogs.Println("TIMEZONE", "change time zone to '"+timeZone+"'")
|
||||
time.Local = location
|
||||
lastTimeZone = timeZone
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 流量统计
|
||||
var inTrafficBytes = uint64(0)
|
||||
var outTrafficBytes = uint64(0)
|
||||
|
||||
// 发送监控流量
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
// 加入到数据队列中
|
||||
if inTrafficBytes > 0 {
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficIn, maps.Map{
|
||||
"total": inTrafficBytes,
|
||||
})
|
||||
}
|
||||
if outTrafficBytes > 0 {
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemTrafficOut, maps.Map{
|
||||
"total": outTrafficBytes,
|
||||
})
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
atomic.StoreUint64(&inTrafficBytes, 0)
|
||||
atomic.StoreUint64(&outTrafficBytes, 0)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// TrafficConn 用于统计流量的连接
|
||||
type TrafficConn struct {
|
||||
rawConn net.Conn
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewTrafficConn(conn net.Conn) net.Conn {
|
||||
return &TrafficConn{rawConn: conn}
|
||||
}
|
||||
|
||||
func (this *TrafficConn) Read(b []byte) (n int, err error) {
|
||||
n, err = this.rawConn.Read(b)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&inTrafficBytes, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *TrafficConn) Write(b []byte) (n int, err error) {
|
||||
n, err = this.rawConn.Write(b)
|
||||
if n > 0 {
|
||||
atomic.AddUint64(&outTrafficBytes, uint64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *TrafficConn) Close() error {
|
||||
this.isClosed = true
|
||||
return this.rawConn.Close()
|
||||
}
|
||||
|
||||
func (this *TrafficConn) LocalAddr() net.Addr {
|
||||
return this.rawConn.LocalAddr()
|
||||
}
|
||||
|
||||
func (this *TrafficConn) RemoteAddr() net.Addr {
|
||||
return this.rawConn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (this *TrafficConn) SetDeadline(t time.Time) error {
|
||||
return this.rawConn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (this *TrafficConn) SetReadDeadline(t time.Time) error {
|
||||
return this.rawConn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (this *TrafficConn) SetWriteDeadline(t time.Time) error {
|
||||
return this.rawConn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (this *TrafficConn) IsClosed() bool {
|
||||
return this.isClosed
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"net"
|
||||
)
|
||||
|
||||
// TrafficListener 用于统计流量的网络监听
|
||||
type TrafficListener struct {
|
||||
rawListener net.Listener
|
||||
}
|
||||
|
||||
func NewTrafficListener(listener net.Listener) net.Listener {
|
||||
return &TrafficListener{rawListener: listener}
|
||||
}
|
||||
|
||||
func (this *TrafficListener) Accept() (net.Conn, error) {
|
||||
conn, err := this.rawListener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 是否在WAF名单中
|
||||
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err == nil {
|
||||
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, ip) && waf.SharedIPBlackList.Contains(waf.IPTypeAll, ip) {
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
return NewTrafficConn(conn), nil
|
||||
}
|
||||
|
||||
func (this *TrafficListener) Close() error {
|
||||
return this.rawListener.Close()
|
||||
}
|
||||
|
||||
func (this *TrafficListener) Addr() net.Addr {
|
||||
return this.rawListener.Addr()
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
policy.Mode = firewallconfigs.FirewallModeDefend
|
||||
}
|
||||
w := &waf.WAF{
|
||||
Id: strconv.FormatInt(policy.Id, 10),
|
||||
Id: policy.Id,
|
||||
IsOn: policy.IsOn,
|
||||
Name: policy.Name,
|
||||
Mode: policy.Mode,
|
||||
@@ -71,7 +71,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
if policy.Inbound != nil && policy.Inbound.IsOn {
|
||||
for _, group := range policy.Inbound.Groups {
|
||||
g := &waf.RuleGroup{
|
||||
Id: strconv.FormatInt(group.Id, 10),
|
||||
Id: group.Id,
|
||||
IsOn: group.IsOn,
|
||||
Name: group.Name,
|
||||
Description: group.Description,
|
||||
@@ -82,7 +82,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
// rule sets
|
||||
for _, set := range group.Sets {
|
||||
s := &waf.RuleSet{
|
||||
Id: strconv.FormatInt(set.Id, 10),
|
||||
Id: set.Id,
|
||||
Code: set.Code,
|
||||
IsOn: set.IsOn,
|
||||
Name: set.Name,
|
||||
@@ -126,7 +126,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
if policy.Outbound != nil && policy.Outbound.IsOn {
|
||||
for _, group := range policy.Outbound.Groups {
|
||||
g := &waf.RuleGroup{
|
||||
Id: strconv.FormatInt(group.Id, 10),
|
||||
Id: group.Id,
|
||||
IsOn: group.IsOn,
|
||||
Name: group.Name,
|
||||
Description: group.Description,
|
||||
@@ -137,7 +137,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
// rule sets
|
||||
for _, set := range group.Sets {
|
||||
s := &waf.RuleSet{
|
||||
Id: strconv.FormatInt(set.Id, 10),
|
||||
Id: set.Id,
|
||||
Code: set.Code,
|
||||
IsOn: set.IsOn,
|
||||
Name: set.Name,
|
||||
|
||||
@@ -5,7 +5,10 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -16,7 +19,9 @@ func init() {
|
||||
ticker := time.NewTicker(60 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
var tr = trackers.Begin("UPLOAD_REMOTE_LOGS")
|
||||
err := uploadLogs()
|
||||
tr.End()
|
||||
if err != nil {
|
||||
logs.Println("[LOG]" + err.Error())
|
||||
}
|
||||
@@ -93,6 +98,18 @@ func Error(tag string, description string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorObject 打印错误对象
|
||||
func ErrorObject(tag string, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if rpc.IsConnError(err) {
|
||||
Warn(tag, err.Error())
|
||||
} else {
|
||||
Error(tag, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ServerError 打印服务相关错误信息
|
||||
func ServerError(serverId int64, tag string, description string) {
|
||||
logs.Println("[" + tag + "]" + description)
|
||||
@@ -144,11 +161,33 @@ func ServerSuccess(serverId int64, tag string, description string) {
|
||||
// 上传日志
|
||||
func uploadLogs() error {
|
||||
logList := []*pb.NodeLog{}
|
||||
|
||||
const hashSize = 5
|
||||
var hashList = []uint64{}
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case log := <-logChan:
|
||||
logList = append(logList, log)
|
||||
// 是否已存在
|
||||
var hash = xxhash.Sum64String(types.String(log.ServerId) + "_" + log.Description)
|
||||
var found = false
|
||||
for _, h := range hashList {
|
||||
if h == hash {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 加入
|
||||
if !found {
|
||||
hashList = append(hashList, hash)
|
||||
if len(hashList) > hashSize {
|
||||
hashList = hashList[1:]
|
||||
}
|
||||
|
||||
logList = append(logList, log)
|
||||
}
|
||||
default:
|
||||
break Loop
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
|
||||
locker sync.Mutex
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
@@ -105,6 +105,18 @@ func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
|
||||
return pb.NewServerServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRPCList() []pb.ServerServiceClient {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var clients = []pb.ServerServiceClient{}
|
||||
for _, conn := range this.conns {
|
||||
clients = append(clients, pb.NewServerServiceClient(conn))
|
||||
}
|
||||
|
||||
return clients
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
|
||||
return pb.NewServerDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -165,15 +177,23 @@ func (this *RPCClient) ClusterContext(clusterId string, clusterSecret string) co
|
||||
|
||||
// Close 关闭连接
|
||||
func (this *RPCClient) Close() {
|
||||
this.locker.Lock()
|
||||
|
||||
for _, conn := range this.conns {
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// UpdateConfig 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
return this.init()
|
||||
|
||||
this.locker.Lock()
|
||||
err := this.init()
|
||||
this.locker.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
|
||||
@@ -2,12 +2,15 @@ package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var sharedRPC *RPCClient = nil
|
||||
var locker = &sync.Mutex{}
|
||||
|
||||
// SharedRPC RPC对象
|
||||
func SharedRPC() (*RPCClient, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
@@ -28,3 +31,18 @@ func SharedRPC() (*RPCClient, error) {
|
||||
sharedRPC = client
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
// IsConnError 是否为连接错误
|
||||
func IsConnError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否为连接错误
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
return statusErr.Code() == codes.Unavailable
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user