Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
130519be71 | ||
|
|
da0fdf6485 | ||
|
|
37f3e6fa2d | ||
|
|
c2151e895d | ||
|
|
e15afc0e0c | ||
|
|
c86f2d5a44 | ||
|
|
d45254b46d | ||
|
|
83df9d0c29 | ||
|
|
856f9d4a3b | ||
|
|
14eda8c276 | ||
|
|
0e732e4821 | ||
|
|
b5b7ab99d3 | ||
|
|
134b56c87a | ||
|
|
a381850dbc | ||
|
|
14abce08c3 | ||
|
|
469a3ea979 | ||
|
|
9b85487a70 | ||
|
|
06ef206c9f | ||
|
|
1c2ca73208 | ||
|
|
b9abc55728 | ||
|
|
21e206061d | ||
|
|
ddebc0e4a8 | ||
|
|
31b16ad67a | ||
|
|
d0b86af4ef | ||
|
|
ce87fa25e7 | ||
|
|
ecbb11eee2 | ||
|
|
9849f14044 | ||
|
|
34fae1c2a3 | ||
|
|
8b22d00ce4 | ||
|
|
1e64386744 | ||
|
|
8dfeda0d80 | ||
|
|
0ece9e0897 | ||
|
|
79ffda4706 | ||
|
|
0d2d7591e5 | ||
|
|
577a5618a1 | ||
|
|
49822ab7e9 | ||
|
|
421cb82505 | ||
|
|
1c0192d773 | ||
|
|
10443dfb15 | ||
|
|
f34ad57e12 | ||
|
|
21c80aea78 | ||
|
|
16c123c400 | ||
|
|
18d301e17f | ||
|
|
dc9a399ff8 | ||
|
|
269e33b9a0 | ||
|
|
581a3d49fc | ||
|
|
8aeb5eb1b6 | ||
|
|
4d79ce7659 | ||
|
|
9fda8c425a | ||
|
|
14259b2ef5 | ||
|
|
211750eb8e | ||
|
|
1050231086 | ||
|
|
034965a697 | ||
|
|
239acf2d17 | ||
|
|
6260fd1009 | ||
|
|
f10ffbaebd | ||
|
|
83f581f7f0 | ||
|
|
03ce082926 | ||
|
|
ebafe458ff | ||
|
|
ff30b8d15c |
@@ -22,7 +22,9 @@ func main() {
|
|||||||
app := apps.NewAppCmd().
|
app := apps.NewAppCmd().
|
||||||
Version(teaconst.Version).
|
Version(teaconst.Version).
|
||||||
Product(teaconst.ProductName).
|
Product(teaconst.ProductName).
|
||||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|service|daemon|pprof]")
|
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof]").
|
||||||
|
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
||||||
|
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove] IP")
|
||||||
|
|
||||||
app.On("test", func() {
|
app.On("test", func() {
|
||||||
err := nodes.NewNode().Test()
|
err := nodes.NewNode().Test()
|
||||||
@@ -30,6 +32,20 @@ func main() {
|
|||||||
_, _ = os.Stderr.WriteString(err.Error())
|
_, _ = os.Stderr.WriteString(err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
app.On("reload", func() {
|
||||||
|
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||||
|
reply, err := sock.Send(&gosock.Command{Code: "reload"})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[ERROR]" + err.Error())
|
||||||
|
} else {
|
||||||
|
var params = maps.NewMap(reply.Params)
|
||||||
|
if params.Has("error") {
|
||||||
|
fmt.Println("[ERROR]" + params.GetString("error"))
|
||||||
|
} else {
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
app.On("daemon", func() {
|
app.On("daemon", func() {
|
||||||
nodes.NewNode().Daemon()
|
nodes.NewNode().Daemon()
|
||||||
})
|
})
|
||||||
@@ -65,6 +81,12 @@ func main() {
|
|||||||
node := nodes.NewNode()
|
node := nodes.NewNode()
|
||||||
node.Start()
|
node.Start()
|
||||||
})
|
})
|
||||||
|
app.On("dbstat", func() {
|
||||||
|
teaconst.EnableDBStat = true
|
||||||
|
|
||||||
|
node := nodes.NewNode()
|
||||||
|
node.Start()
|
||||||
|
})
|
||||||
app.On("trackers", func() {
|
app.On("trackers", func() {
|
||||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||||
reply, err := sock.Send(&gosock.Command{Code: "trackers"})
|
reply, err := sock.Send(&gosock.Command{Code: "trackers"})
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -8,15 +8,14 @@ replace (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||||
github.com/andybalholm/brotli v1.0.3
|
github.com/andybalholm/brotli v1.0.4
|
||||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||||
github.com/cespare/xxhash v1.1.0
|
github.com/cespare/xxhash v1.1.0
|
||||||
github.com/chai2010/webp v1.1.0 // indirect
|
github.com/chai2010/webp v1.1.0 // indirect
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/fsnotify/fsnotify v1.5.1
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24
|
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475
|
||||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
||||||
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8
|
github.com/iwind/gowebp v0.0.0-20211029040624-7331ecc78ed8
|
||||||
@@ -26,15 +25,12 @@ require (
|
|||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/mssola/user_agent v0.5.3
|
github.com/mssola/user_agent v0.5.3
|
||||||
github.com/pires/go-proxyproto v0.6.1
|
github.com/pires/go-proxyproto v0.6.1
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil/v3 v3.22.2
|
||||||
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||||
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
google.golang.org/grpc v1.45.0
|
||||||
google.golang.org/grpc v1.43.0
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
)
|
)
|
||||||
|
|||||||
71
go.sum
71
go.sum
@@ -6,8 +6,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
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 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
|
||||||
@@ -23,7 +23,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
@@ -38,7 +37,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -52,8 +50,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
|||||||
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
|
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@@ -75,15 +71,16 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475 h1:EseyfFaQOjWanGiby9KMw7PjDBMg/95tLDgIw/ns0Cw=
|
||||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24 h1:1cGulkD2SNJJRok5OKwyhP/Ddm+PgSWKOupn0cR36/A=
|
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
|
||||||
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 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
||||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
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 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
|
||||||
@@ -100,6 +97,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||||
@@ -122,10 +121,12 @@ github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldO
|
|||||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -134,10 +135,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
|
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||||
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
|
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||||
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
|
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
|
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
|
||||||
@@ -171,8 +172,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
|||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||||
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -195,17 +196,21 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d h1:1oIt9o40TWWI9FUaveVpUvBe13FNqBNVXy3ue2fcfkw=
|
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||||
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||||
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -230,19 +235,21 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
|
google.golang.org/genproto v0.0.0-20220303160752-862486edd9cc h1:fb/ViRpv3ln/LvbqZtTpoOd1YQDNH12gaGZreoSFovE=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20220303160752-862486edd9cc/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
|
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
|
||||||
|
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
|
||||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
|
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||||
|
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -269,10 +276,8 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
type AppCmd struct {
|
type AppCmd struct {
|
||||||
product string
|
product string
|
||||||
version string
|
version string
|
||||||
usage string
|
usages []string
|
||||||
options []*CommandHelpOption
|
options []*CommandHelpOption
|
||||||
appendStrings []string
|
appendStrings []string
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ func (this *AppCmd) Version(version string) *AppCmd {
|
|||||||
|
|
||||||
// Usage 使用方法
|
// Usage 使用方法
|
||||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||||
this.usage = usage
|
this.usages = append(this.usages, usage)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +77,10 @@ func (this *AppCmd) Append(appendString string) *AppCmd {
|
|||||||
func (this *AppCmd) Print() {
|
func (this *AppCmd) Print() {
|
||||||
fmt.Println(this.product + " v" + this.version)
|
fmt.Println(this.product + " v" + this.version)
|
||||||
|
|
||||||
usage := this.usage
|
fmt.Println("Usage:")
|
||||||
fmt.Println("Usage:", "\n "+usage)
|
for _, usage := range this.usages {
|
||||||
|
fmt.Println(" " + usage)
|
||||||
|
}
|
||||||
|
|
||||||
if len(this.options) > 0 {
|
if len(this.options) > 0 {
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
|||||||
10
internal/caches/consts.go
Normal file
10
internal/caches/consts.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
const (
|
||||||
|
SuffixWebP = "@GOEDGE_WEBP" // WebP后缀
|
||||||
|
SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding
|
||||||
|
SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod
|
||||||
|
SuffixPartial = "@GOEDGE_partial" // 分区缓存后缀
|
||||||
|
)
|
||||||
@@ -6,9 +6,12 @@ import "errors"
|
|||||||
|
|
||||||
// 常用的几个错误
|
// 常用的几个错误
|
||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("cache not found")
|
ErrNotFound = errors.New("cache not found")
|
||||||
ErrFileIsWriting = errors.New("the file is writing")
|
ErrFileIsWriting = errors.New("the file is writing")
|
||||||
ErrInvalidRange = errors.New("invalid range")
|
ErrInvalidRange = errors.New("invalid range")
|
||||||
|
ErrEntityTooLarge = errors.New("entity too large")
|
||||||
|
ErrWritingUnavailable = errors.New("writing unavailable")
|
||||||
|
ErrWritingQueueFull = errors.New("writing queue full")
|
||||||
)
|
)
|
||||||
|
|
||||||
// CapacityError 容量错误
|
// CapacityError 容量错误
|
||||||
@@ -30,7 +33,10 @@ func CanIgnoreErr(err error) bool {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if err == ErrFileIsWriting {
|
if err == ErrFileIsWriting ||
|
||||||
|
err == ErrEntityTooLarge ||
|
||||||
|
err == ErrWritingUnavailable ||
|
||||||
|
err == ErrWritingQueueFull {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_, ok := err.(*CapacityError)
|
_, ok := err.(*CapacityError)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
type HotItem struct {
|
type HotItem struct {
|
||||||
Key string
|
Key string
|
||||||
ExpiresAt int64
|
Hits uint32
|
||||||
Hits uint32
|
|
||||||
Status int
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,52 +4,32 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||||
"github.com/iwind/TeaGo/lists"
|
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const CountFileDB = 20
|
||||||
|
|
||||||
// FileList 文件缓存列表管理
|
// FileList 文件缓存列表管理
|
||||||
type FileList struct {
|
type FileList struct {
|
||||||
dir string
|
dir string
|
||||||
db *sql.DB
|
dbList [CountFileDB]*FileListDB
|
||||||
total int64
|
total int64
|
||||||
|
|
||||||
onAdd func(item *Item)
|
onAdd func(item *Item)
|
||||||
onRemove func(item *Item)
|
onRemove func(item *Item)
|
||||||
|
|
||||||
// cacheItems
|
|
||||||
existsByHashStmt *sql.Stmt // 根据hash检查是否存在
|
|
||||||
insertStmt *sql.Stmt // 写入数据
|
|
||||||
selectByHashStmt *sql.Stmt // 使用hash查询数据
|
|
||||||
deleteByHashStmt *sql.Stmt // 根据hash删除数据
|
|
||||||
statStmt *sql.Stmt // 统计
|
|
||||||
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
|
|
||||||
isReady bool
|
|
||||||
|
|
||||||
memoryCache *ttlcache.Cache
|
memoryCache *ttlcache.Cache
|
||||||
|
|
||||||
|
// 老数据库地址
|
||||||
|
oldDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileList(dir string) ListInterface {
|
func NewFileList(dir string) ListInterface {
|
||||||
@@ -59,6 +39,10 @@ func NewFileList(dir string) ListInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileList) SetOldDir(oldDir string) {
|
||||||
|
this.oldDir = oldDir
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileList) Init() error {
|
func (this *FileList) Init() error {
|
||||||
// 检查目录是否存在
|
// 检查目录是否存在
|
||||||
_, err := os.Stat(this.dir)
|
_, err := os.Stat(this.dir)
|
||||||
@@ -70,114 +54,38 @@ func (this *FileList) Init() error {
|
|||||||
remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'")
|
remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'")
|
||||||
}
|
}
|
||||||
|
|
||||||
this.itemsTableName = "cacheItems_v3"
|
|
||||||
this.hitsTableName = "hits"
|
|
||||||
|
|
||||||
var dir = this.dir
|
var dir = this.dir
|
||||||
if dir == "/" {
|
if dir == "/" {
|
||||||
// 防止sqlite提示authority错误
|
// 防止sqlite提示authority错误
|
||||||
dir = ""
|
dir = ""
|
||||||
}
|
}
|
||||||
var dbPath = dir + "/index.db"
|
|
||||||
remotelogs.Println("CACHE", "loading database '"+dbPath+"'")
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=shared&mode=rwc&_journal_mode=WAL")
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("open database failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
db.SetMaxOpenConns(1)
|
remotelogs.Println("CACHE", "loading database from '"+dir+"' ...")
|
||||||
|
for i := 0; i < CountFileDB; i++ {
|
||||||
|
var db = NewFileListDB()
|
||||||
|
err = db.Open(dir + "/db-" + types.String(i) + ".db")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
this.db = db
|
err = db.Init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO 耗时过长,暂时不整理数据库
|
this.dbList[i] = db
|
||||||
/**_, err = db.Exec("VACUUM")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}**/
|
|
||||||
|
|
||||||
// 创建
|
|
||||||
err = this.initTables(db, 1)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("init tables failed: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除旧表
|
|
||||||
// 这个一定要在initTables()之后,因为老的数据需要转移
|
|
||||||
this.oldTables = []string{
|
|
||||||
"cacheItems",
|
|
||||||
"cacheItems_v2",
|
|
||||||
}
|
|
||||||
err = this.removeOldTables()
|
|
||||||
if err != nil {
|
|
||||||
remotelogs.Warn("CACHE", "clean old tables failed: "+err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取总数量
|
// 读取总数量
|
||||||
row := this.db.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
this.total = 0
|
||||||
if row.Err() != nil {
|
for _, db := range this.dbList {
|
||||||
return row.Err()
|
this.total += db.total
|
||||||
}
|
|
||||||
var total int64
|
|
||||||
err = row.Scan(&total)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
this.total = total
|
|
||||||
|
|
||||||
// 常用语句
|
|
||||||
this.existsByHashStmt, err = this.db.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.insertStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
// 升级老版本数据库
|
||||||
if err != nil {
|
goman.New(func() {
|
||||||
return err
|
this.upgradeOldDB()
|
||||||
}
|
})
|
||||||
|
|
||||||
this.selectByHashStmt, err = this.db.Prepare(`SELECT "key", "headerSize", "bodySize", "metaSize", "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? LIMIT 1`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
this.deleteByHashStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
this.statStmt, err = this.db.Prepare(`SELECT COUNT(*), IFNULL(SUM(headerSize+bodySize+metaSize), 0), IFNULL(SUM(headerSize+bodySize), 0) FROM "` + this.itemsTableName + `"`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
this.purgeStmt, err = this.db.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" WHERE staleAt<=? LIMIT ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
this.deleteAllStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemsTableName + `"`)
|
|
||||||
if err != nil {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isReady = true
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -188,26 +96,22 @@ func (this *FileList) Reset() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) Add(hash string, item *Item) error {
|
func (this *FileList) Add(hash string, item *Item) error {
|
||||||
if !this.isReady {
|
var db = this.getDB(hash)
|
||||||
|
|
||||||
|
if !db.IsReady() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.StaleAt == 0 {
|
err := db.Add(hash, item)
|
||||||
item.StaleAt = item.ExpiredAt
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = this.insertHitStmt.Exec(hash, timeutil.Format("YW"))
|
atomic.AddInt64(&this.total, 1)
|
||||||
if err != nil {
|
|
||||||
return err
|
// 这里不增加点击量,以减少对数据库的操作次数
|
||||||
}
|
|
||||||
|
|
||||||
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
||||||
atomic.AddInt64(&this.total, 1)
|
|
||||||
|
|
||||||
if this.onAdd != nil {
|
if this.onAdd != nil {
|
||||||
this.onAdd(item)
|
this.onAdd(item)
|
||||||
@@ -216,40 +120,36 @@ func (this *FileList) Add(hash string, item *Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) Exist(hash string) (bool, error) {
|
func (this *FileList) Exist(hash string) (bool, error) {
|
||||||
if !this.isReady {
|
var db = this.getDB(hash)
|
||||||
|
|
||||||
|
if !db.IsReady() {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
item := this.memoryCache.Read(hash)
|
var item = this.memoryCache.Read(hash)
|
||||||
if item != nil {
|
if item != nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := this.existsByHashStmt.Query(hash, time.Now().Unix())
|
var row = db.existsByHashStmt.QueryRow(hash, time.Now().Unix())
|
||||||
|
if row.Err() != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiredAt int64
|
||||||
|
err := row.Scan(&expiredAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer func() {
|
this.memoryCache.Write(hash, 1, expiredAt)
|
||||||
_ = rows.Close()
|
return true, nil
|
||||||
}()
|
|
||||||
if rows.Next() {
|
|
||||||
var expiredAt int64
|
|
||||||
err = rows.Scan(&expiredAt)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
this.memoryCache.Write(hash, 1, expiredAt)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanPrefix 清理某个前缀的缓存数据
|
// CleanPrefix 清理某个前缀的缓存数据
|
||||||
func (this *FileList) CleanPrefix(prefix string) error {
|
func (this *FileList) CleanPrefix(prefix string) error {
|
||||||
if !this.isReady {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(prefix) == 0 {
|
if len(prefix) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -258,106 +158,48 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
|||||||
this.memoryCache.Clean()
|
this.memoryCache.Clean()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var count = int64(10000)
|
for _, db := range this.dbList {
|
||||||
var staleLife = 600 // TODO 需要可以设置
|
err := db.CleanPrefix(prefix)
|
||||||
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
|
||||||
for {
|
|
||||||
result, err := this.db.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
affectedRows, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if affectedRows < count {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) Remove(hash string) error {
|
func (this *FileList) Remove(hash string) error {
|
||||||
if !this.isReady {
|
_, err := this.remove(hash)
|
||||||
return nil
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
// 从缓存中删除
|
|
||||||
this.memoryCache.Delete(hash)
|
|
||||||
|
|
||||||
row := this.selectByHashStmt.QueryRow(hash)
|
|
||||||
if row.Err() != nil {
|
|
||||||
return row.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = &Item{Type: ItemTypeFile}
|
|
||||||
err := row.Scan(&item.Key, &item.HeaderSize, &item.BodySize, &item.MetaSize, &item.ExpiredAt)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = this.deleteByHashStmt.Exec(hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = this.deleteHitByHashStmt.Exec(hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddInt64(&this.total, -1)
|
|
||||||
|
|
||||||
if this.onRemove != nil {
|
|
||||||
this.onRemove(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge 清理过期的缓存
|
// Purge 清理过期的缓存
|
||||||
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
|
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
|
||||||
// callback 每次发现过期key的调用
|
// callback 每次发现过期key的调用
|
||||||
func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) {
|
func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||||
if !this.isReady {
|
count /= CountFileDB
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if count <= 0 {
|
if count <= 0 {
|
||||||
count = 1000
|
count = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := this.purgeStmt.Query(time.Now().Unix(), count)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hashStrings := []string{}
|
|
||||||
var countFound = 0
|
var countFound = 0
|
||||||
for rows.Next() {
|
for _, db := range this.dbList {
|
||||||
var hash string
|
hashStrings, err := db.ListExpiredItems(count)
|
||||||
err = rows.Scan(&hash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = rows.Close()
|
return 0, nil
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
hashStrings = append(hashStrings, hash)
|
countFound += len(hashStrings)
|
||||||
countFound++
|
|
||||||
}
|
|
||||||
_ = rows.Close() // 不能使用defer,防止读写冲突
|
|
||||||
|
|
||||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||||
for _, hash := range hashStrings {
|
for _, hash := range hashStrings {
|
||||||
err = this.Remove(hash)
|
err = this.Remove(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = callback(hash)
|
err = callback(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,80 +207,80 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error {
|
func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error {
|
||||||
if !this.isReady {
|
count /= CountFileDB
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if count <= 0 {
|
if count <= 0 {
|
||||||
return nil
|
count = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := this.lfuHitsStmt.Query(count)
|
for _, db := range this.dbList {
|
||||||
if err != nil {
|
hashStrings, err := db.ListLFUItems(count)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hashStrings := []string{}
|
|
||||||
var countFound = 0
|
|
||||||
for rows.Next() {
|
|
||||||
var hash string
|
|
||||||
err = rows.Scan(&hash)
|
|
||||||
if err != nil {
|
|
||||||
_ = rows.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hashStrings = append(hashStrings, hash)
|
|
||||||
countFound++
|
|
||||||
}
|
|
||||||
_ = rows.Close() // 不能使用defer,防止读写冲突
|
|
||||||
|
|
||||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
|
||||||
for _, hash := range hashStrings {
|
|
||||||
err = this.Remove(hash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = callback(hash)
|
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||||
if err != nil {
|
for _, hash := range hashStrings {
|
||||||
return err
|
notFound, err := this.remove(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if notFound {
|
||||||
|
_, err = db.deleteHitByHashStmt.Exec(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = callback(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) CleanAll() error {
|
func (this *FileList) CleanAll() error {
|
||||||
if !this.isReady {
|
defer this.memoryCache.Clean()
|
||||||
return nil
|
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
err := db.CleanAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.memoryCache.Clean()
|
|
||||||
|
|
||||||
_, err := this.deleteAllStmt.Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
atomic.StoreInt64(&this.total, 0)
|
atomic.StoreInt64(&this.total, 0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||||
if !this.isReady {
|
var result = &Stat{}
|
||||||
return &Stat{}, nil
|
|
||||||
|
for _, db := range this.dbList {
|
||||||
|
if !db.IsReady() {
|
||||||
|
return &Stat{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里不设置过期时间、不使用 check 函数,目的是让查询更快速一些
|
||||||
|
_ = check
|
||||||
|
|
||||||
|
row := db.statStmt.QueryRow()
|
||||||
|
if row.Err() != nil {
|
||||||
|
return nil, row.Err()
|
||||||
|
}
|
||||||
|
var stat = &Stat{}
|
||||||
|
err := row.Scan(&stat.Count, &stat.Size, &stat.ValueSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.Count += stat.Count
|
||||||
|
result.Size += stat.Size
|
||||||
|
result.ValueSize += stat.ValueSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这里不设置过期时间、不使用 check 函数,目的是让查询更快速一些
|
return result, nil
|
||||||
row := this.statStmt.QueryRow()
|
|
||||||
if row.Err() != nil {
|
|
||||||
return nil, row.Err()
|
|
||||||
}
|
|
||||||
stat := &Stat{}
|
|
||||||
err := row.Scan(&stat.Count, &stat.Size, &stat.ValueSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stat, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count 总数量
|
// Count 总数量
|
||||||
@@ -449,9 +291,13 @@ func (this *FileList) Count() (int64, error) {
|
|||||||
|
|
||||||
// IncreaseHit 增加点击量
|
// IncreaseHit 增加点击量
|
||||||
func (this *FileList) IncreaseHit(hash string) error {
|
func (this *FileList) IncreaseHit(hash string) error {
|
||||||
var week = timeutil.Format("YW")
|
var db = this.getDB(hash)
|
||||||
_, err := this.increaseHitStmt.Exec(hash, week, week, week, week)
|
|
||||||
return err
|
if !db.IsReady() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.IncreaseHit(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnAdd 添加事件
|
// OnAdd 添加事件
|
||||||
@@ -465,162 +311,177 @@ func (this *FileList) OnRemove(f func(item *Item)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileList) Close() error {
|
func (this *FileList) Close() error {
|
||||||
this.isClosed = true
|
|
||||||
this.isReady = false
|
|
||||||
|
|
||||||
this.memoryCache.Destroy()
|
this.memoryCache.Destroy()
|
||||||
|
|
||||||
if this.db != nil {
|
for _, db := range this.dbList {
|
||||||
_ = this.existsByHashStmt.Close()
|
if db != nil {
|
||||||
_ = this.insertStmt.Close()
|
_ = db.Close()
|
||||||
_ = this.selectByHashStmt.Close()
|
}
|
||||||
_ = this.deleteByHashStmt.Close()
|
|
||||||
_ = this.statStmt.Close()
|
|
||||||
_ = this.purgeStmt.Close()
|
|
||||||
_ = this.deleteAllStmt.Close()
|
|
||||||
|
|
||||||
_ = this.insertHitStmt.Close()
|
|
||||||
_ = this.increaseHitStmt.Close()
|
|
||||||
_ = this.deleteHitByHashStmt.Close()
|
|
||||||
_ = this.lfuHitsStmt.Close()
|
|
||||||
|
|
||||||
return this.db.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化
|
func (this *FileList) GetDBIndex(hash string) uint64 {
|
||||||
func (this *FileList) initTables(db *sql.DB, times int) error {
|
return fnv.HashString(hash) % CountFileDB
|
||||||
// 检查是否存在
|
}
|
||||||
_, err := db.Exec(`SELECT id FROM "` + this.itemsTableName + `" LIMIT 1`)
|
|
||||||
var notFound = false
|
func (this *FileList) getDB(hash string) *FileListDB {
|
||||||
|
return this.dbList[fnv.HashString(hash)%CountFileDB]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||||
|
var db = this.getDB(hash)
|
||||||
|
|
||||||
|
if !db.IsReady() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从缓存中删除
|
||||||
|
this.memoryCache.Delete(hash)
|
||||||
|
|
||||||
|
var row = db.selectByHashStmt.QueryRow(hash)
|
||||||
|
if row.Err() != nil {
|
||||||
|
if row.Err() == sql.ErrNoRows {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, row.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = &Item{Type: ItemTypeFile}
|
||||||
|
err = row.Scan(&item.Key, &item.HeaderSize, &item.BodySize, &item.MetaSize, &item.ExpiredAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notFound = true
|
if err == sql.ErrNoRows {
|
||||||
}
|
return true, nil
|
||||||
|
|
||||||
{
|
|
||||||
// expiredAt - 过期时间,用来判断有无过期
|
|
||||||
// staleAt - 陈旧最大时间,用来清理缓存
|
|
||||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"hash" varchar(32),
|
|
||||||
"key" varchar(1024),
|
|
||||||
"headerSize" integer DEFAULT 0,
|
|
||||||
"bodySize" integer DEFAULT 0,
|
|
||||||
"metaSize" integer DEFAULT 0,
|
|
||||||
"expiredAt" integer DEFAULT 0,
|
|
||||||
"staleAt" integer DEFAULT 0,
|
|
||||||
"createdAt" integer DEFAULT 0,
|
|
||||||
"host" varchar(128),
|
|
||||||
"serverId" integer
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "createdAt"
|
|
||||||
ON "` + this.itemsTableName + `" (
|
|
||||||
"createdAt" ASC
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "expiredAt"
|
|
||||||
ON "` + this.itemsTableName + `" (
|
|
||||||
"expiredAt" ASC
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "staleAt"
|
|
||||||
ON "` + this.itemsTableName + `" (
|
|
||||||
"staleAt" ASC
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
|
||||||
ON "` + this.itemsTableName + `" (
|
|
||||||
"hash" ASC
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS "serverId"
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果数据为空,从老数据中加载数据
|
_, err = db.deleteByHashStmt.Exec(hash)
|
||||||
if notFound {
|
if err != nil {
|
||||||
// v2 => v3
|
return false, err
|
||||||
remotelogs.Println("CACHE", "transferring old data from v2 to v3 ...")
|
|
||||||
result, err := db.Exec(`INSERT INTO "` + this.itemsTableName + `" ("id", "hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "createdAt", "host", "serverId", "staleAt") SELECT "id", "hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "createdAt", "host", "serverId", "expiredAt"+600 FROM cacheItems_v2`)
|
|
||||||
if err == nil {
|
|
||||||
count, _ := result.RowsAffected()
|
|
||||||
remotelogs.Println("CACHE", "transfer old data from v2 to v3 finished, "+types.String(count)+" rows transferred")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
atomic.AddInt64(&this.total, -1)
|
||||||
_, 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"
|
_, err = db.deleteHitByHashStmt.Exec(hash)
|
||||||
ON "` + this.hitsTableName + `" (
|
if err != nil {
|
||||||
"hash" ASC
|
return false, err
|
||||||
);
|
|
||||||
`)
|
|
||||||
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
|
if this.onRemove != nil {
|
||||||
|
this.onRemove(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除过期不用的表格
|
// 升级老版本数据库
|
||||||
func (this *FileList) removeOldTables() error {
|
func (this *FileList) upgradeOldDB() {
|
||||||
rows, err := this.db.Query(`SELECT "name" FROM sqlite_master WHERE "type"='table'`)
|
if len(this.oldDir) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = this.UpgradeV3(this.oldDir, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||||
|
// index.db
|
||||||
|
var indexDBPath = oldDir + "/index.db"
|
||||||
|
_, err := os.Stat(indexDBPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
remotelogs.Println("CACHE", "upgrading local database from '"+oldDir+"' ...")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = os.Remove(indexDBPath)
|
||||||
|
remotelogs.Println("CACHE", "upgrading local database finished")
|
||||||
|
}()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite3", "file:"+indexDBPath+"?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = rows.Close()
|
_ = db.Close()
|
||||||
}()
|
}()
|
||||||
for rows.Next() {
|
|
||||||
var name string
|
var isFinished = false
|
||||||
err = rows.Scan(&name)
|
var offset = 0
|
||||||
|
var count = 10000
|
||||||
|
|
||||||
|
for {
|
||||||
|
if isFinished {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = func() error {
|
||||||
|
defer func() {
|
||||||
|
offset += count
|
||||||
|
}()
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT "hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "createdAt", "host", "serverId" FROM "cacheItems_v3" ORDER BY "id" ASC LIMIT ?, ?`, offset, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = rows.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var hash = ""
|
||||||
|
var key = ""
|
||||||
|
var headerSize int64
|
||||||
|
var bodySize int64
|
||||||
|
var metaSize int64
|
||||||
|
var expiredAt int64
|
||||||
|
var staleAt int64
|
||||||
|
var createdAt int64
|
||||||
|
var host string
|
||||||
|
var serverId int64
|
||||||
|
|
||||||
|
isFinished = true
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
isFinished = false
|
||||||
|
|
||||||
|
err = rows.Scan(&hash, &key, &headerSize, &bodySize, &metaSize, &expiredAt, &staleAt, &createdAt, &host, &serverId)
|
||||||
|
if err != nil {
|
||||||
|
if brokenOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = this.Add(hash, &Item{
|
||||||
|
Type: ItemTypeFile,
|
||||||
|
Key: key,
|
||||||
|
ExpiredAt: expiredAt,
|
||||||
|
StaleAt: staleAt,
|
||||||
|
HeaderSize: headerSize,
|
||||||
|
BodySize: bodySize,
|
||||||
|
MetaSize: metaSize,
|
||||||
|
Host: host,
|
||||||
|
ServerId: serverId,
|
||||||
|
Week1Hits: 0,
|
||||||
|
Week2Hits: 0,
|
||||||
|
Week: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if brokenOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if lists.ContainsString(this.oldTables, name) {
|
|
||||||
// 异步执行
|
|
||||||
goman.New(func() {
|
|
||||||
remotelogs.Println("CACHE", "remove old table '"+name+"' ...")
|
|
||||||
_, _ = this.db.Exec(`DROP TABLE "` + name + `"`)
|
|
||||||
remotelogs.Println("CACHE", "remove old table '"+name+"' done")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
484
internal/caches/list_file_db.go
Normal file
484
internal/caches/list_file_db.go
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileListDB struct {
|
||||||
|
readDB *dbs.DB
|
||||||
|
writeDB *dbs.DB
|
||||||
|
|
||||||
|
itemsTableName string
|
||||||
|
hitsTableName string
|
||||||
|
|
||||||
|
total int64
|
||||||
|
|
||||||
|
isClosed bool
|
||||||
|
isReady bool
|
||||||
|
|
||||||
|
// cacheItems
|
||||||
|
existsByHashStmt *dbs.Stmt // 根据hash检查是否存在
|
||||||
|
insertStmt *dbs.Stmt // 写入数据
|
||||||
|
selectByHashStmt *dbs.Stmt // 使用hash查询数据
|
||||||
|
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
||||||
|
statStmt *dbs.Stmt // 统计
|
||||||
|
purgeStmt *dbs.Stmt // 清理
|
||||||
|
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||||
|
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||||
|
|
||||||
|
// hits
|
||||||
|
insertHitStmt *dbs.Stmt // 写入数据
|
||||||
|
increaseHitStmt *dbs.Stmt // 增加点击量
|
||||||
|
deleteHitByHashStmt *dbs.Stmt // 根据hash删除数据
|
||||||
|
lfuHitsStmt *dbs.Stmt // 读取老的数据
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileListDB() *FileListDB {
|
||||||
|
return &FileListDB{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) Open(dbPath string) error {
|
||||||
|
// write db
|
||||||
|
writeDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=32000&_secure_delete=FAST")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("open write database failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDB.SetMaxOpenConns(1)
|
||||||
|
|
||||||
|
// TODO 耗时过长,暂时不整理数据库
|
||||||
|
/**_, err = db.Exec("VACUUM")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}**/
|
||||||
|
|
||||||
|
this.writeDB = dbs.NewDB(writeDB)
|
||||||
|
|
||||||
|
if teaconst.EnableDBStat {
|
||||||
|
this.writeDB.EnableStat(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read db
|
||||||
|
readDB, err := sql.Open("sqlite3", "file:"+dbPath+"?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=32000")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("open read database failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
readDB.SetMaxOpenConns(runtime.NumCPU())
|
||||||
|
|
||||||
|
this.readDB = dbs.NewDB(readDB)
|
||||||
|
|
||||||
|
if teaconst.EnableDBStat {
|
||||||
|
this.readDB.EnableStat(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) Init() error {
|
||||||
|
this.itemsTableName = "cacheItems"
|
||||||
|
this.hitsTableName = "hits"
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
var err = this.initTables(1)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("init tables failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取总数量
|
||||||
|
row := this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||||
|
if row.Err() != nil {
|
||||||
|
return row.Err()
|
||||||
|
}
|
||||||
|
var total int64
|
||||||
|
err = row.Scan(&total)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
this.total = total
|
||||||
|
|
||||||
|
// 常用语句
|
||||||
|
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.insertStmt, err = this.writeDB.Prepare(`INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectByHashStmt, err = this.readDB.Prepare(`SELECT "key", "headerSize", "bodySize", "metaSize", "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? LIMIT 1`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteByHashStmt, err = this.writeDB.Prepare(`DELETE FROM "` + this.itemsTableName + `" WHERE "hash"=?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.statStmt, err = this.readDB.Prepare(`SELECT COUNT(*), IFNULL(SUM(headerSize+bodySize+metaSize), 0), IFNULL(SUM(headerSize+bodySize), 0) FROM "` + this.itemsTableName + `"`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.purgeStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" WHERE staleAt<=? LIMIT ?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteAllStmt, err = this.writeDB.Prepare(`DELETE FROM "` + this.itemsTableName + `"`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
|
||||||
|
|
||||||
|
this.insertHitStmt, err = this.writeDB.Prepare(`INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`)
|
||||||
|
|
||||||
|
this.increaseHitStmt, err = this.writeDB.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.writeDB.Prepare(`DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lfuHitsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.hitsTableName + `" ORDER BY "week" ASC, "week1Hits"+"week2Hits" ASC LIMIT ?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isReady = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) IsReady() bool {
|
||||||
|
return this.isReady
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) Total() int64 {
|
||||||
|
return this.total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) Add(hash string, item *Item) error {
|
||||||
|
if item.StaleAt == 0 {
|
||||||
|
item.StaleAt = item.ExpiredAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// 放入队列
|
||||||
|
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, utils.UnixTime())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) ListExpiredItems(count int) (hashList []string, err error) {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count <= 0 {
|
||||||
|
count = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := this.purgeStmt.Query(time.Now().Unix(), count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = rows.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var hash string
|
||||||
|
err = rows.Scan(&hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashList = append(hashList, hash)
|
||||||
|
}
|
||||||
|
return hashList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count <= 0 {
|
||||||
|
count = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
hashList, err = this.listLFUItems(count)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hashList) > count/2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不足补齐
|
||||||
|
olderHashList, err := this.listOlderItems(count - len(hashList))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashList = append(hashList, olderHashList...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) IncreaseHit(hash string) error {
|
||||||
|
var week = timeutil.Format("YW")
|
||||||
|
_, err := this.increaseHitStmt.Exec(hash, week, week, week, week)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) CleanPrefix(prefix string) error {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var count = int64(10000)
|
||||||
|
var staleLife = 600 // TODO 需要可以设置
|
||||||
|
var unixTime = utils.UnixTime() // 只删除当前的,不删除新的
|
||||||
|
for {
|
||||||
|
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
affectedRows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if affectedRows < count {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) CleanAll() error {
|
||||||
|
if !this.isReady {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := this.deleteAllStmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) Close() error {
|
||||||
|
this.isClosed = true
|
||||||
|
this.isReady = false
|
||||||
|
|
||||||
|
if this.existsByHashStmt != nil {
|
||||||
|
_ = this.existsByHashStmt.Close()
|
||||||
|
}
|
||||||
|
if this.insertStmt != nil {
|
||||||
|
_ = this.insertStmt.Close()
|
||||||
|
}
|
||||||
|
if this.selectByHashStmt != nil {
|
||||||
|
_ = this.selectByHashStmt.Close()
|
||||||
|
}
|
||||||
|
if this.deleteByHashStmt != nil {
|
||||||
|
_ = this.deleteByHashStmt.Close()
|
||||||
|
}
|
||||||
|
if this.statStmt != nil {
|
||||||
|
_ = this.statStmt.Close()
|
||||||
|
}
|
||||||
|
if this.purgeStmt != nil {
|
||||||
|
_ = this.purgeStmt.Close()
|
||||||
|
}
|
||||||
|
if this.deleteAllStmt != nil {
|
||||||
|
_ = this.deleteAllStmt.Close()
|
||||||
|
}
|
||||||
|
if this.listOlderItemsStmt != nil {
|
||||||
|
_ = this.listOlderItemsStmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.insertHitStmt != nil {
|
||||||
|
_ = this.insertHitStmt.Close()
|
||||||
|
}
|
||||||
|
if this.increaseHitStmt != nil {
|
||||||
|
_ = this.increaseHitStmt.Close()
|
||||||
|
}
|
||||||
|
if this.deleteHitByHashStmt != nil {
|
||||||
|
_ = this.deleteHitByHashStmt.Close()
|
||||||
|
}
|
||||||
|
if this.lfuHitsStmt != nil {
|
||||||
|
_ = this.lfuHitsStmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var errStrings []string
|
||||||
|
|
||||||
|
if this.readDB != nil {
|
||||||
|
err := this.readDB.Close()
|
||||||
|
if err != nil {
|
||||||
|
errStrings = append(errStrings, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.writeDB != nil {
|
||||||
|
err := this.writeDB.Close()
|
||||||
|
if err != nil {
|
||||||
|
errStrings = append(errStrings, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errStrings) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("close database failed: " + strings.Join(errStrings, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
func (this *FileListDB) initTables(times int) error {
|
||||||
|
{
|
||||||
|
// expiredAt - 过期时间,用来判断有无过期
|
||||||
|
// staleAt - 陈旧最大时间,用来清理缓存
|
||||||
|
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemsTableName + `" (
|
||||||
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"hash" varchar(32),
|
||||||
|
"key" varchar(1024),
|
||||||
|
"tag" varchar(64),
|
||||||
|
"headerSize" integer DEFAULT 0,
|
||||||
|
"bodySize" integer DEFAULT 0,
|
||||||
|
"metaSize" integer DEFAULT 0,
|
||||||
|
"expiredAt" integer DEFAULT 0,
|
||||||
|
"staleAt" integer DEFAULT 0,
|
||||||
|
"createdAt" integer DEFAULT 0,
|
||||||
|
"host" varchar(128),
|
||||||
|
"serverId" integer
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "createdAt"
|
||||||
|
ON "` + this.itemsTableName + `" (
|
||||||
|
"createdAt" ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "expiredAt"
|
||||||
|
ON "` + this.itemsTableName + `" (
|
||||||
|
"expiredAt" ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "staleAt"
|
||||||
|
ON "` + this.itemsTableName + `" (
|
||||||
|
"staleAt" ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
||||||
|
ON "` + this.itemsTableName + `" (
|
||||||
|
"hash" ASC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "serverId"
|
||||||
|
ON "` + this.itemsTableName + `" (
|
||||||
|
"serverId" ASC
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 尝试删除重建
|
||||||
|
if times < 3 {
|
||||||
|
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.itemsTableName + `"`)
|
||||||
|
if dropErr == nil {
|
||||||
|
return this.initTables(times + 1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
|
||||||
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"hash" varchar(32),
|
||||||
|
"week1Hits" integer DEFAULT 0,
|
||||||
|
"week2Hits" integer DEFAULT 0,
|
||||||
|
"week" varchar(6)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
|
||||||
|
ON "` + this.hitsTableName + `" (
|
||||||
|
"hash" ASC
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
// 尝试删除重建
|
||||||
|
if times < 3 {
|
||||||
|
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
|
||||||
|
if dropErr == nil {
|
||||||
|
return this.initTables(times + 1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) listLFUItems(count int) (hashList []string, err error) {
|
||||||
|
rows, err := this.lfuHitsStmt.Query(count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = rows.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var hash string
|
||||||
|
err = rows.Scan(&hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashList = append(hashList, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
|
||||||
|
rows, err := this.listOlderItemsStmt.Query(count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = rows.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var hash string
|
||||||
|
err = rows.Scan(&hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashList = append(hashList, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashList, nil
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package caches
|
package caches_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
@@ -15,23 +16,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFileList_Init(t *testing.T) {
|
func TestFileList_Init(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Add(t *testing.T) {
|
func TestFileList_Add(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = list.Add(stringutil.Md5("123456"), &Item{
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var hash = stringutil.Md5("123456")
|
||||||
|
t.Log("db index:", list.GetDBIndex(hash))
|
||||||
|
err = list.Add(hash, &caches.Item{
|
||||||
Key: "123456",
|
Key: "123456",
|
||||||
ExpiredAt: time.Now().Unix(),
|
ExpiredAt: time.Now().Unix() + 1,
|
||||||
HeaderSize: 1,
|
HeaderSize: 1,
|
||||||
MetaSize: 2,
|
MetaSize: 2,
|
||||||
BodySize: 3,
|
BodySize: 3,
|
||||||
@@ -41,19 +52,27 @@ func TestFileList_Add(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Log(list.Exist(hash))
|
||||||
|
|
||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Add_Many(t *testing.T) {
|
func TestFileList_Add_Many(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
for i := 0; i < 2000_0000; i++ {
|
for i := 0; i < 100_000; i++ {
|
||||||
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||||
_ = list.Add(stringutil.Md5(u), &Item{
|
_ = list.Add(stringutil.Md5(u), &caches.Item{
|
||||||
Key: u,
|
Key: u,
|
||||||
ExpiredAt: time.Now().Unix() + 3600,
|
ExpiredAt: time.Now().Unix() + 3600,
|
||||||
HeaderSize: 1,
|
HeaderSize: 1,
|
||||||
@@ -72,36 +91,46 @@ func TestFileList_Add_Many(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Exist(t *testing.T) {
|
func TestFileList_Exist(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
before := time.Now()
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
total, _ := list.Count()
|
||||||
|
t.Log("total:", total)
|
||||||
|
|
||||||
|
var before = time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
}()
|
}()
|
||||||
{
|
{
|
||||||
exists, err := list.Exist(stringutil.Md5("123456"))
|
var hash = stringutil.Md5("123456")
|
||||||
|
exists, err := list.Exist(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log("exists:", exists)
|
t.Log(hash, "exists:", exists)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
exists, err := list.Exist(stringutil.Md5("http://edge.teaos.cn/1234561"))
|
var hash = stringutil.Md5("http://edge.teaos.cn/1234561")
|
||||||
|
exists, err := list.Exist(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log("exists:", exists)
|
t.Log(hash, "exists:", exists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Exist_Many_DB(t *testing.T) {
|
func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||||
// 测试在多个数据库下的性能
|
// 测试在多个数据库下的性能
|
||||||
var listSlice = []ListInterface{}
|
var listSlice = []caches.ListInterface{}
|
||||||
for i := 1; i <= 10; i++ {
|
for i := 1; i <= 10; i++ {
|
||||||
list := NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
list := caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -151,13 +180,18 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_CleanPrefix(t *testing.T) {
|
func TestFileList_CleanPrefix(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
err = list.CleanPrefix("1234")
|
err = list.CleanPrefix("123")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -165,12 +199,17 @@ func TestFileList_CleanPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Remove(t *testing.T) {
|
func TestFileList_Remove(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
list.OnRemove(func(item *Item) {
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
list.OnRemove(func(item *caches.Item) {
|
||||||
t.Logf("remove %#v", item)
|
t.Logf("remove %#v", item)
|
||||||
})
|
})
|
||||||
err = list.Remove(stringutil.Md5("123456"))
|
err = list.Remove(stringutil.Md5("123456"))
|
||||||
@@ -178,30 +217,71 @@ func TestFileList_Remove(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
|
|
||||||
|
t.Log("===count===")
|
||||||
|
t.Log(list.Count())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Purge(t *testing.T) {
|
func TestFileList_Purge(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = list.Purge(2, func(hash string) error {
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
_, err = list.Purge(caches.CountFileDB*2, func(hash string) error {
|
||||||
t.Log(hash)
|
t.Log(hash)
|
||||||
|
count++
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log("ok")
|
t.Log("ok, purged", count, "items")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Stat(t *testing.T) {
|
func TestFileList_PurgeLFU(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = list.IncreaseHit(stringutil.Md5("123456"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
err = list.PurgeLFU(caches.CountFileDB*2, func(hash string) error {
|
||||||
|
t.Log(hash)
|
||||||
|
count++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok, purged", count, "items")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileList_Stat(t *testing.T) {
|
||||||
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
|
err := list.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
stat, err := list.Stat(nil)
|
stat, err := list.Stat(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -210,7 +290,7 @@ func TestFileList_Stat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Count(t *testing.T) {
|
func TestFileList_Count(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
list := caches.NewFileList(Tea.Root + "/data")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -225,7 +305,7 @@ func TestFileList_Count(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_CleanAll(t *testing.T) {
|
func TestFileList_CleanAll(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
list := caches.NewFileList(Tea.Root + "/data")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -238,58 +318,17 @@ func TestFileList_CleanAll(t *testing.T) {
|
|||||||
t.Log(list.Count())
|
t.Log(list.Count())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileList_Conflict(t *testing.T) {
|
|
||||||
list := NewFileList(Tea.Root + "/data").(*FileList)
|
|
||||||
err := list.Init()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := list.purgeStmt.Query(time.Now().Unix(), 1000)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
_ = rows.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Log("before exists")
|
|
||||||
t.Log(list.Exist("123456"))
|
|
||||||
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) {
|
func TestFileList_IncreaseHit(t *testing.T) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
var before = time.Now()
|
var before = time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
@@ -303,13 +342,32 @@ func TestFileList_IncreaseHit(t *testing.T) {
|
|||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileList_UpgradeV3(t *testing.T) {
|
||||||
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||||
|
err := list.Init()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = list.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = list.UpgradeV3("/Users/WorkSpace/EdgeProject/EdgeCache/p43", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkFileList_Exist(b *testing.B) {
|
func BenchmarkFileList_Exist(b *testing.B) {
|
||||||
list := NewFileList(Tea.Root + "/data")
|
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||||
err := list.Init()
|
err := list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f")
|
_, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func init() {
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
// 全局配置
|
// 全局配置
|
||||||
MaxDiskCapacity *shared.SizeCapacity
|
MaxDiskCapacity *shared.SizeCapacity
|
||||||
|
DiskDir string
|
||||||
MaxMemoryCapacity *shared.SizeCapacity
|
MaxMemoryCapacity *shared.SizeCapacity
|
||||||
|
|
||||||
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
||||||
@@ -49,6 +50,11 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
|||||||
|
|
||||||
newPolicyIds := []int64{}
|
newPolicyIds := []int64{}
|
||||||
for _, policy := range newPolicies {
|
for _, policy := range newPolicies {
|
||||||
|
// 使用节点单独的缓存目录
|
||||||
|
if len(this.DiskDir) > 0 {
|
||||||
|
policy.UpdateDiskDir(this.DiskDir)
|
||||||
|
}
|
||||||
|
|
||||||
newPolicyIds = append(newPolicyIds, policy.Id)
|
newPolicyIds = append(newPolicyIds, policy.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +105,19 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
|||||||
} else {
|
} else {
|
||||||
// 检查policy是否有变化
|
// 检查policy是否有变化
|
||||||
if !storage.Policy().IsSame(policy) {
|
if !storage.Policy().IsSame(policy) {
|
||||||
remotelogs.Println("CACHE", "policy "+strconv.FormatInt(policy.Id, 10)+" changed")
|
// 检查是否可以直接修改
|
||||||
|
if storage.CanUpdatePolicy(policy) {
|
||||||
|
err := policy.Init()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("CACHE", "reload policy '"+types.String(policy.Id)+"' failed: init policy failed: "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remotelogs.Println("CACHE", "reload policy '"+types.String(policy.Id)+"'")
|
||||||
|
storage.UpdatePolicy(policy)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
remotelogs.Println("CACHE", "restart policy '"+types.String(policy.Id)+"'")
|
||||||
|
|
||||||
// 停止老的
|
// 停止老的
|
||||||
storage.Stop()
|
storage.Stop()
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestManager_UpdatePolicies(t *testing.T) {
|
func TestManager_UpdatePolicies(t *testing.T) {
|
||||||
{
|
{
|
||||||
policies := []*serverconfigs.HTTPCachePolicy{}
|
var policies = []*serverconfigs.HTTPCachePolicy{}
|
||||||
SharedManager.UpdatePolicies(policies)
|
SharedManager.UpdatePolicies(policies)
|
||||||
printManager(t)
|
printManager(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
policies := []*serverconfigs.HTTPCachePolicy{
|
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||||
{
|
{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
@@ -42,7 +43,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
policies := []*serverconfigs.HTTPCachePolicy{
|
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||||
{
|
{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
Type: serverconfigs.CachePolicyStorageFile,
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
@@ -71,6 +72,50 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestManager_ChangePolicy_Memory(t *testing.T) {
|
||||||
|
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
Type: serverconfigs.CachePolicyStorageMemory,
|
||||||
|
Options: map[string]interface{}{},
|
||||||
|
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
SharedManager.UpdatePolicies(policies)
|
||||||
|
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
Type: serverconfigs.CachePolicyStorageMemory,
|
||||||
|
Options: map[string]interface{}{},
|
||||||
|
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_ChangePolicy_File(t *testing.T) {
|
||||||
|
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"dir": Tea.Root + "/data/cache-index/p1",
|
||||||
|
},
|
||||||
|
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
SharedManager.UpdatePolicies(policies)
|
||||||
|
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
Type: serverconfigs.CachePolicyStorageFile,
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"dir": Tea.Root + "/data/cache-index/p1",
|
||||||
|
},
|
||||||
|
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func printManager(t *testing.T) {
|
func printManager(t *testing.T) {
|
||||||
t.Log("===manager==")
|
t.Log("===manager==")
|
||||||
t.Log("storage:")
|
t.Log("storage:")
|
||||||
|
|||||||
@@ -4,28 +4,37 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PartialRanges 内容分区范围定义
|
// PartialRanges 内容分区范围定义
|
||||||
type PartialRanges struct {
|
type PartialRanges struct {
|
||||||
ranges [][2]int64
|
Ranges [][2]int64 `json:"ranges"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialRanges 获取新对象
|
// NewPartialRanges 获取新对象
|
||||||
func NewPartialRanges() *PartialRanges {
|
func NewPartialRanges() *PartialRanges {
|
||||||
return &PartialRanges{ranges: [][2]int64{}}
|
return &PartialRanges{Ranges: [][2]int64{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialRangesFromJSON 从JSON中解析范围
|
// NewPartialRangesFromJSON 从JSON中解析范围
|
||||||
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
||||||
var rs = [][2]int64{}
|
var rs = NewPartialRanges()
|
||||||
err := json.Unmarshal(data, &rs)
|
err := json.Unmarshal(data, &rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var r = NewPartialRanges()
|
|
||||||
r.ranges = rs
|
return rs, nil
|
||||||
return r, nil
|
}
|
||||||
|
|
||||||
|
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPartialRangesFromJSON(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 添加新范围
|
// Add 添加新范围
|
||||||
@@ -36,46 +45,41 @@ func (this *PartialRanges) Add(begin int64, end int64) {
|
|||||||
|
|
||||||
var nr = [2]int64{begin, end}
|
var nr = [2]int64{begin, end}
|
||||||
|
|
||||||
var count = len(this.ranges)
|
var count = len(this.Ranges)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
this.ranges = [][2]int64{nr}
|
this.Ranges = [][2]int64{nr}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert
|
// insert
|
||||||
// TODO 将来使用二分法改进
|
// TODO 将来使用二分法改进
|
||||||
var index = -1
|
var index = -1
|
||||||
for i, r := range this.ranges {
|
for i, r := range this.Ranges {
|
||||||
if r[0] > begin || (r[0] == begin && r[1] >= end) {
|
if r[0] > begin || (r[0] == begin && r[1] >= end) {
|
||||||
index = i
|
index = i
|
||||||
this.ranges = append(this.ranges, [2]int64{})
|
this.Ranges = append(this.Ranges, [2]int64{})
|
||||||
copy(this.ranges[index+1:], this.ranges[index:])
|
copy(this.Ranges[index+1:], this.Ranges[index:])
|
||||||
this.ranges[index] = nr
|
this.Ranges[index] = nr
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
index = count
|
index = count
|
||||||
this.ranges = append(this.ranges, nr)
|
this.Ranges = append(this.Ranges, nr)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.merge(index)
|
this.merge(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ranges 获取所有范围
|
|
||||||
func (this *PartialRanges) Ranges() [][2]int64 {
|
|
||||||
return this.ranges
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains 检查是否包含某个范围
|
// Contains 检查是否包含某个范围
|
||||||
func (this *PartialRanges) Contains(begin int64, end int64) bool {
|
func (this *PartialRanges) Contains(begin int64, end int64) bool {
|
||||||
if len(this.ranges) == 0 {
|
if len(this.Ranges) == 0 {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 使用二分法查找改进性能
|
// TODO 使用二分法查找改进性能
|
||||||
for _, r2 := range this.ranges {
|
for _, r2 := range this.Ranges {
|
||||||
if r2[0] <= begin && r2[1] >= end {
|
if r2[0] <= begin && r2[1] >= end {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -84,23 +88,65 @@ func (this *PartialRanges) Contains(begin int64, end int64) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nearest 查找最近的某个范围
|
||||||
|
func (this *PartialRanges) Nearest(begin int64, end int64) (r [2]int64, ok bool) {
|
||||||
|
if len(this.Ranges) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 使用二分法查找改进性能
|
||||||
|
for _, r2 := range this.Ranges {
|
||||||
|
if r2[0] <= begin && r2[1] > begin {
|
||||||
|
r = [2]int64{begin, this.min(end, r2[1])}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// AsJSON 转换为JSON
|
// AsJSON 转换为JSON
|
||||||
func (this *PartialRanges) AsJSON() ([]byte, error) {
|
func (this *PartialRanges) AsJSON() ([]byte, error) {
|
||||||
return json.Marshal(this.ranges)
|
return json.Marshal(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToFile 写入到文件中
|
||||||
|
func (this *PartialRanges) WriteToFile(path string) error {
|
||||||
|
data, err := this.AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("convert to json failed: " + err.Error())
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(path, data, 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromFile 从文件中读取
|
||||||
|
func (this *PartialRanges) ReadFromFile(path string) (*PartialRanges, error) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPartialRangesFromJSON(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PartialRanges) Max() int64 {
|
||||||
|
if len(this.Ranges) > 0 {
|
||||||
|
return this.Ranges[len(this.Ranges)-1][1]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *PartialRanges) merge(index int) {
|
func (this *PartialRanges) merge(index int) {
|
||||||
// forward
|
// forward
|
||||||
var lastIndex = index
|
var lastIndex = index
|
||||||
for i := index; i >= 1; i-- {
|
for i := index; i >= 1; i-- {
|
||||||
var curr = this.ranges[i]
|
var curr = this.Ranges[i]
|
||||||
var prev = this.ranges[i-1]
|
var prev = this.Ranges[i-1]
|
||||||
var w1 = this.w(curr)
|
var w1 = this.w(curr)
|
||||||
var w2 = this.w(prev)
|
var w2 = this.w(prev)
|
||||||
if w1+w2 >= this.max(curr[1], prev[1])-this.min(curr[0], prev[0])-1 {
|
if w1+w2 >= this.max(curr[1], prev[1])-this.min(curr[0], prev[0])-1 {
|
||||||
prev = [2]int64{this.min(curr[0], prev[0]), this.max(curr[1], prev[1])}
|
prev = [2]int64{this.min(curr[0], prev[0]), this.max(curr[1], prev[1])}
|
||||||
this.ranges[i-1] = prev
|
this.Ranges[i-1] = prev
|
||||||
this.ranges = append(this.ranges[:i], this.ranges[i+1:]...)
|
this.Ranges = append(this.Ranges[:i], this.Ranges[i+1:]...)
|
||||||
lastIndex = i - 1
|
lastIndex = i - 1
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -109,15 +155,15 @@ func (this *PartialRanges) merge(index int) {
|
|||||||
|
|
||||||
// backward
|
// backward
|
||||||
index = lastIndex
|
index = lastIndex
|
||||||
for index < len(this.ranges)-1 {
|
for index < len(this.Ranges)-1 {
|
||||||
var curr = this.ranges[index]
|
var curr = this.Ranges[index]
|
||||||
var next = this.ranges[index+1]
|
var next = this.Ranges[index+1]
|
||||||
var w1 = this.w(curr)
|
var w1 = this.w(curr)
|
||||||
var w2 = this.w(next)
|
var w2 = this.w(next)
|
||||||
if w1+w2 >= this.max(curr[1], next[1])-this.min(curr[0], next[0])-1 {
|
if w1+w2 >= this.max(curr[1], next[1])-this.min(curr[0], next[0])-1 {
|
||||||
curr = [2]int64{this.min(curr[0], next[0]), this.max(curr[1], next[1])}
|
curr = [2]int64{this.min(curr[0], next[0]), this.max(curr[1], next[1])}
|
||||||
this.ranges = append(this.ranges[:index], this.ranges[index+1:]...)
|
this.Ranges = append(this.Ranges[:index], this.Ranges[index+1:]...)
|
||||||
this.ranges[index] = curr
|
this.Ranges[index] = curr
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ func TestNewPartialRanges(t *testing.T) {
|
|||||||
r.Add(200, 1000)
|
r.Add(200, 1000)
|
||||||
r.Add(200, 10040)
|
r.Add(200, 10040)
|
||||||
|
|
||||||
logs.PrintAsJSON(r.Ranges())
|
logs.PrintAsJSON(r.Ranges, t)
|
||||||
|
t.Log("max:", r.Max())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPartialRanges1(t *testing.T) {
|
func TestNewPartialRanges1(t *testing.T) {
|
||||||
@@ -35,7 +36,7 @@ func TestNewPartialRanges1(t *testing.T) {
|
|||||||
r.Add(200, 300)
|
r.Add(200, 300)
|
||||||
r.Add(1, 1000)
|
r.Add(1, 1000)
|
||||||
|
|
||||||
var rs = r.Ranges()
|
var rs = r.Ranges
|
||||||
logs.PrintAsJSON(rs, t)
|
logs.PrintAsJSON(rs, t)
|
||||||
a.IsTrue(len(rs) == 1)
|
a.IsTrue(len(rs) == 1)
|
||||||
if len(rs) == 1 {
|
if len(rs) == 1 {
|
||||||
@@ -56,7 +57,7 @@ func TestNewPartialRanges2(t *testing.T) {
|
|||||||
r.Add(303, 304)
|
r.Add(303, 304)
|
||||||
r.Add(250, 400)
|
r.Add(250, 400)
|
||||||
|
|
||||||
var rs = r.Ranges()
|
var rs = r.Ranges
|
||||||
logs.PrintAsJSON(rs, t)
|
logs.PrintAsJSON(rs, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ func TestNewPartialRanges3(t *testing.T) {
|
|||||||
r.Add(200, 300)
|
r.Add(200, 300)
|
||||||
r.Add(250, 400)
|
r.Add(250, 400)
|
||||||
|
|
||||||
var rs = r.Ranges()
|
var rs = r.Ranges
|
||||||
logs.PrintAsJSON(rs, t)
|
logs.PrintAsJSON(rs, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ func TestNewPartialRanges4(t *testing.T) {
|
|||||||
r.Add(410, 415)
|
r.Add(410, 415)
|
||||||
r.Add(400, 409)
|
r.Add(400, 409)
|
||||||
|
|
||||||
var rs = r.Ranges()
|
var rs = r.Ranges
|
||||||
logs.PrintAsJSON(rs, t)
|
logs.PrintAsJSON(rs, t)
|
||||||
t.Log(r.Contains(400, 416))
|
t.Log(r.Contains(400, 416))
|
||||||
}
|
}
|
||||||
@@ -93,7 +94,57 @@ func TestNewPartialRanges5(t *testing.T) {
|
|||||||
for j := 0; j < 1000; j++ {
|
for j := 0; j < 1000; j++ {
|
||||||
r.Add(int64(j), int64(j+100))
|
r.Add(int64(j), int64(j+100))
|
||||||
}
|
}
|
||||||
logs.PrintAsJSON(r.Ranges(), t)
|
logs.PrintAsJSON(r.Ranges, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewPartialRanges_Nearest(t *testing.T) {
|
||||||
|
{
|
||||||
|
// nearby
|
||||||
|
var r = caches.NewPartialRanges()
|
||||||
|
r.Add(301, 400)
|
||||||
|
r.Add(401, 500)
|
||||||
|
r.Add(501, 600)
|
||||||
|
|
||||||
|
t.Log(r.Nearest(100, 200))
|
||||||
|
t.Log(r.Nearest(300, 350))
|
||||||
|
t.Log(r.Nearest(302, 350))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// nearby
|
||||||
|
var r = caches.NewPartialRanges()
|
||||||
|
r.Add(301, 400)
|
||||||
|
r.Add(450, 500)
|
||||||
|
r.Add(550, 600)
|
||||||
|
|
||||||
|
t.Log(r.Nearest(100, 200))
|
||||||
|
t.Log(r.Nearest(300, 350))
|
||||||
|
t.Log(r.Nearest(302, 350))
|
||||||
|
t.Log(r.Nearest(302, 440))
|
||||||
|
t.Log(r.Nearest(302, 1000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewPartialRanges_Large_Range(t *testing.T) {
|
||||||
|
var a = assert.NewAssertion(t)
|
||||||
|
|
||||||
|
var largeSize int64 = 10000000000000
|
||||||
|
t.Log(largeSize/1024/1024/1024, "G")
|
||||||
|
|
||||||
|
var r = caches.NewPartialRanges()
|
||||||
|
r.Add(1, largeSize)
|
||||||
|
jsonData, err := r.AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(string(jsonData))
|
||||||
|
|
||||||
|
r2, err := caches.NewPartialRangesFromJSON(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.IsTrue(largeSize == r2.Ranges[0][1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPartialRanges_AsJSON(t *testing.T) {
|
func TestNewPartialRanges_AsJSON(t *testing.T) {
|
||||||
@@ -111,7 +162,7 @@ func TestNewPartialRanges_AsJSON(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log(r2.Ranges())
|
t.Log(r2.Ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkNewPartialRanges(b *testing.B) {
|
func BenchmarkNewPartialRanges(b *testing.B) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
|
import "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
|
|
||||||
type ReaderFunc func(n int) (goNext bool, err error)
|
type ReaderFunc func(n int) (goNext bool, err error)
|
||||||
|
|
||||||
type Reader interface {
|
type Reader interface {
|
||||||
@@ -36,6 +38,9 @@ type Reader interface {
|
|||||||
// BodySize Body Size
|
// BodySize Body Size
|
||||||
BodySize() int64
|
BodySize() int64
|
||||||
|
|
||||||
|
// ContainsRange 是否包含某个区间内容
|
||||||
|
ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool)
|
||||||
|
|
||||||
// Close 关闭
|
// Close 关闭
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package caches
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -332,6 +333,11 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainsRange 是否包含某些区间内容
|
||||||
|
func (this *FileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool) {
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileReader) Close() error {
|
func (this *FileReader) Close() error {
|
||||||
if this.openFileCache != nil {
|
if this.openFileCache != nil {
|
||||||
if this.isClosed {
|
if this.isClosed {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -197,6 +198,11 @@ func (this *MemoryReader) ReadBodyRange(buf []byte, start int64, end int64, call
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainsRange 是否包含某些区间内容
|
||||||
|
func (this *MemoryReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool) {
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
func (this *MemoryReader) Close() error {
|
func (this *MemoryReader) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
144
internal/caches/reader_partial_file.go
Normal file
144
internal/caches/reader_partial_file.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package caches
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PartialFileReader struct {
|
||||||
|
*FileReader
|
||||||
|
|
||||||
|
ranges *PartialRanges
|
||||||
|
rangePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPartialFileReader(fp *os.File) *PartialFileReader {
|
||||||
|
return &PartialFileReader{
|
||||||
|
FileReader: NewFileReader(fp),
|
||||||
|
rangePath: partialRangesFilePath(fp.Name()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileReader) Init() error {
|
||||||
|
return this.InitAutoDiscard(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||||
|
if this.openFile != nil {
|
||||||
|
this.meta = this.openFile.meta
|
||||||
|
this.header = this.openFile.header
|
||||||
|
}
|
||||||
|
|
||||||
|
isOk := false
|
||||||
|
|
||||||
|
if autoDiscard {
|
||||||
|
defer func() {
|
||||||
|
if !isOk {
|
||||||
|
_ = this.discard()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取Range
|
||||||
|
ranges, err := NewPartialRangesFromFile(this.rangePath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("read ranges failed: " + err.Error())
|
||||||
|
}
|
||||||
|
this.ranges = ranges
|
||||||
|
|
||||||
|
var buf = this.meta
|
||||||
|
if len(buf) == 0 {
|
||||||
|
buf = make([]byte, SizeMeta)
|
||||||
|
ok, err := this.readToBuff(this.fp, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
this.meta = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
this.status = status
|
||||||
|
|
||||||
|
// URL
|
||||||
|
urlLength := binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
|
||||||
|
|
||||||
|
// header
|
||||||
|
headerSize := int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
|
||||||
|
if headerSize == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
this.headerSize = headerSize
|
||||||
|
this.headerOffset = int64(SizeMeta) + int64(urlLength)
|
||||||
|
|
||||||
|
// body
|
||||||
|
this.bodyOffset = this.headerOffset + int64(headerSize)
|
||||||
|
bodySize := int(binary.BigEndian.Uint64(buf[SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength+SizeBodyLength]))
|
||||||
|
if bodySize == 0 {
|
||||||
|
isOk = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
this.bodySize = int64(bodySize)
|
||||||
|
|
||||||
|
// read header
|
||||||
|
if this.openFileCache != nil && len(this.header) == 0 {
|
||||||
|
if headerSize > 0 && headerSize <= 512 {
|
||||||
|
this.header = make([]byte, headerSize)
|
||||||
|
_, err := this.fp.Seek(this.headerOffset, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = this.readToBuff(this.fp, this.header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOk = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsRange 是否包含某些区间内容
|
||||||
|
// 这里的 r 是已经经过格式化的
|
||||||
|
func (this *PartialFileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool) {
|
||||||
|
r2, ok = this.ranges.Nearest(r.Start(), r.End())
|
||||||
|
if ok && this.bodySize > 0 {
|
||||||
|
// 考虑可配置
|
||||||
|
var span int64 = 512 * 1024
|
||||||
|
if this.bodySize > 1<<30 {
|
||||||
|
span = 1 << 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多
|
||||||
|
if r2.Length() < r.Length() && r2.Length() < span {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLength 获取区间最大长度
|
||||||
|
func (this *PartialFileReader) MaxLength() int64 {
|
||||||
|
if this.bodySize > 0 {
|
||||||
|
return this.bodySize
|
||||||
|
}
|
||||||
|
return this.ranges.Max() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileReader) discard() error {
|
||||||
|
_ = os.Remove(this.rangePath)
|
||||||
|
return this.FileReader.discard()
|
||||||
|
}
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/logs"
|
|
||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
@@ -44,7 +47,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HotItemSize = 1024
|
HotItemSize = 1024 // 热点数据数量
|
||||||
|
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||||
|
FileToMemoryMaxSize int64 = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||||
)
|
)
|
||||||
|
|
||||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||||
@@ -55,7 +60,7 @@ var sharedWritingFileKeyLocker = sync.Mutex{}
|
|||||||
// [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data]
|
// [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data]
|
||||||
type FileStorage struct {
|
type FileStorage struct {
|
||||||
policy *serverconfigs.HTTPCachePolicy
|
policy *serverconfigs.HTTPCachePolicy
|
||||||
cacheConfig *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
||||||
memoryStorage *MemoryStorage // 一级缓存
|
memoryStorage *MemoryStorage // 一级缓存
|
||||||
totalSize int64
|
totalSize int64
|
||||||
|
|
||||||
@@ -68,6 +73,8 @@ type FileStorage struct {
|
|||||||
lastHotSize int
|
lastHotSize int
|
||||||
hotTicker *utils.Ticker
|
hotTicker *utils.Ticker
|
||||||
|
|
||||||
|
ignoreKeys *setutils.FixedSet
|
||||||
|
|
||||||
openFileCache *OpenFileCache
|
openFileCache *OpenFileCache
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +83,7 @@ func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
|||||||
policy: policy,
|
policy: policy,
|
||||||
hotMap: map[string]*HotItem{},
|
hotMap: map[string]*HotItem{},
|
||||||
lastHotSize: -1,
|
lastHotSize: -1,
|
||||||
|
ignoreKeys: setutils.NewFixedSet(32768),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,41 +92,125 @@ func (this *FileStorage) Policy() *serverconfigs.HTTPCachePolicy {
|
|||||||
return this.policy
|
return this.policy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanUpdatePolicy 检查策略是否可以更新
|
||||||
|
func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
|
||||||
|
// 检查路径是否有变化
|
||||||
|
oldOptionsJSON, err := json.Marshal(this.policy.Options)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var oldOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||||
|
err = json.Unmarshal(oldOptionsJSON, oldOptions)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
newOptionsJSON, err := json.Marshal(newPolicy.Options)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var newOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||||
|
err = json.Unmarshal(newOptionsJSON, newOptions)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldOptions.Dir == newOptions.Dir {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePolicy 修改策略
|
||||||
|
func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) {
|
||||||
|
var oldOpenFileCache = this.options.OpenFileCache
|
||||||
|
|
||||||
|
this.policy = newPolicy
|
||||||
|
|
||||||
|
newOptionsJSON, err := json.Marshal(newPolicy.Options)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var newOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||||
|
err = json.Unmarshal(newOptionsJSON, newOptions)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: decode options failed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = newOptions.Init()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: init options failed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = newOptions
|
||||||
|
|
||||||
|
var memoryStorage = this.memoryStorage
|
||||||
|
if memoryStorage != nil {
|
||||||
|
if newOptions.MemoryPolicy != nil && newOptions.MemoryPolicy.CapacityBytes() > 0 {
|
||||||
|
memoryStorage.UpdatePolicy(newOptions.MemoryPolicy)
|
||||||
|
} else {
|
||||||
|
memoryStorage.Stop()
|
||||||
|
this.memoryStorage = nil
|
||||||
|
}
|
||||||
|
} else if newOptions.MemoryPolicy != nil && this.options.MemoryPolicy.Capacity != nil && this.options.MemoryPolicy.Capacity.Count > 0 {
|
||||||
|
err = this.createMemoryStorage()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: create memory storage failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// open cache
|
||||||
|
oldOpenFileCacheJSON, _ := json.Marshal(oldOpenFileCache)
|
||||||
|
newOpenFileCacheJSON, _ := json.Marshal(this.options.OpenFileCache)
|
||||||
|
if bytes.Compare(oldOpenFileCacheJSON, newOpenFileCacheJSON) != 0 {
|
||||||
|
this.initOpenFileCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge Ticker
|
||||||
|
if newPolicy.PersistenceAutoPurgeInterval != this.policy.PersistenceAutoPurgeInterval {
|
||||||
|
this.initPurgeTicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Init 初始化
|
// Init 初始化
|
||||||
func (this *FileStorage) Init() error {
|
func (this *FileStorage) Init() error {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
before := time.Now()
|
var before = time.Now()
|
||||||
|
|
||||||
// 配置
|
// 配置
|
||||||
cacheConfig := &serverconfigs.HTTPFileCacheStorage{}
|
var options = &serverconfigs.HTTPFileCacheStorage{}
|
||||||
optionsJSON, err := json.Marshal(this.policy.Options)
|
optionsJSON, err := json.Marshal(this.policy.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(optionsJSON, cacheConfig)
|
err = json.Unmarshal(optionsJSON, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
this.cacheConfig = cacheConfig
|
this.options = options
|
||||||
|
|
||||||
if !filepath.IsAbs(this.cacheConfig.Dir) {
|
if !filepath.IsAbs(this.options.Dir) {
|
||||||
this.cacheConfig.Dir = Tea.Root + Tea.DS + this.cacheConfig.Dir
|
this.options.Dir = Tea.Root + Tea.DS + this.options.Dir
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cacheConfig.Dir = filepath.Clean(this.cacheConfig.Dir)
|
this.options.Dir = filepath.Clean(this.options.Dir)
|
||||||
var dir = this.cacheConfig.Dir
|
var dir = this.options.Dir
|
||||||
|
|
||||||
if len(dir) == 0 {
|
if len(dir) == 0 {
|
||||||
return errors.New("[CACHE]cache storage dir can not be empty")
|
return errors.New("[CACHE]cache storage dir can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
list := NewFileList(dir + "/p" + strconv.FormatInt(this.policy.Id, 10))
|
var list = NewFileList(dir + "/p" + types.String(this.policy.Id) + "/.indexes")
|
||||||
err = list.Init()
|
err = list.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||||
this.list = list
|
this.list = list
|
||||||
stat, err := list.Stat(func(hash string) bool {
|
stat, err := list.Stat(func(hash string) bool {
|
||||||
return true
|
return true
|
||||||
@@ -149,8 +241,8 @@ func (this *FileStorage) Init() error {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// 统计
|
// 统计
|
||||||
count := stat.Count
|
var count = stat.Count
|
||||||
size := stat.Size
|
var size = stat.Size
|
||||||
|
|
||||||
cost := time.Since(before).Seconds() * 1000
|
cost := time.Since(before).Seconds() * 1000
|
||||||
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
||||||
@@ -161,7 +253,7 @@ func (this *FileStorage) Init() error {
|
|||||||
} else if size > 1024 {
|
} else if size > 1024 {
|
||||||
sizeMB = fmt.Sprintf("%.3f K", float64(size)/1024)
|
sizeMB = fmt.Sprintf("%.3f K", float64(size)/1024)
|
||||||
}
|
}
|
||||||
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.cacheConfig.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
|
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 初始化list
|
// 初始化list
|
||||||
@@ -171,64 +263,38 @@ func (this *FileStorage) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载内存缓存
|
// 加载内存缓存
|
||||||
if this.cacheConfig.MemoryPolicy != nil {
|
if this.options.MemoryPolicy != nil && this.options.MemoryPolicy.Capacity != nil && this.options.MemoryPolicy.Capacity.Count > 0 {
|
||||||
if this.cacheConfig.MemoryPolicy.Capacity != nil && this.cacheConfig.MemoryPolicy.Capacity.Count > 0 {
|
err = this.createMemoryStorage()
|
||||||
memoryPolicy := &serverconfigs.HTTPCachePolicy{
|
if err != nil {
|
||||||
Id: this.policy.Id,
|
return err
|
||||||
IsOn: this.policy.IsOn,
|
|
||||||
Name: this.policy.Name,
|
|
||||||
Description: this.policy.Description,
|
|
||||||
Capacity: this.cacheConfig.MemoryPolicy.Capacity,
|
|
||||||
MaxKeys: this.policy.MaxKeys,
|
|
||||||
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
|
||||||
Type: serverconfigs.CachePolicyStorageMemory,
|
|
||||||
Options: this.policy.Options,
|
|
||||||
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, this)
|
|
||||||
err = memoryStorage.Init()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
this.memoryStorage = memoryStorage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// open file cache
|
// open file cache
|
||||||
if this.cacheConfig.OpenFileCache != nil && this.cacheConfig.OpenFileCache.IsOn && this.cacheConfig.OpenFileCache.Max > 0 {
|
this.initOpenFileCache()
|
||||||
this.openFileCache, err = NewOpenFileCache(this.cacheConfig.OpenFileCache.Max)
|
|
||||||
logs.Println("start open file cache")
|
|
||||||
if err != nil {
|
|
||||||
remotelogs.Error("CACHE", "open file cache failed: "+err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileStorage) OpenReader(key string, useStale bool) (Reader, error) {
|
func (this *FileStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
|
||||||
return this.openReader(key, true, useStale)
|
return this.openReader(key, true, useStale, isPartial)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool) (Reader, error) {
|
func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool, isPartial bool) (Reader, error) {
|
||||||
// 使用陈旧缓存的时候,我们认为是短暂的,只需要从文件里检查即可
|
// 使用陈旧缓存的时候,我们认为是短暂的,只需要从文件里检查即可
|
||||||
if useStale {
|
if useStale {
|
||||||
allowMemory = false
|
allowMemory = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 区间缓存只存在文件中
|
||||||
|
if isPartial {
|
||||||
|
allowMemory = false
|
||||||
|
}
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
if allowMemory && this.memoryStorage != nil {
|
var memoryStorage = this.memoryStorage
|
||||||
reader, err := this.memoryStorage.OpenReader(key, useStale)
|
if allowMemory && memoryStorage != nil {
|
||||||
|
reader, err := memoryStorage.OpenReader(key, useStale, isPartial)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return reader, err
|
return reader, err
|
||||||
}
|
}
|
||||||
@@ -250,8 +316,9 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool)
|
|||||||
// TODO 尝试使用mmap加快读取速度
|
// TODO 尝试使用mmap加快读取速度
|
||||||
var isOk = false
|
var isOk = false
|
||||||
var openFile *OpenFile
|
var openFile *OpenFile
|
||||||
if this.openFileCache != nil {
|
var openFileCache = this.openFileCache // 因为中间可能有修改,所以先赋值再获取
|
||||||
openFile = this.openFileCache.Get(path)
|
if openFileCache != nil {
|
||||||
|
openFile = openFileCache.Get(path)
|
||||||
}
|
}
|
||||||
var fp *os.File
|
var fp *os.File
|
||||||
var err error
|
var err error
|
||||||
@@ -269,13 +336,22 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool)
|
|||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
if !isOk {
|
||||||
_ = fp.Close()
|
_ = fp.Close()
|
||||||
_ = os.Remove(path)
|
_ = this.removeCacheFile(path)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var reader = NewFileReader(fp)
|
var reader Reader
|
||||||
reader.openFile = openFile
|
if isPartial {
|
||||||
reader.openFileCache = this.openFileCache
|
var partialFileReader = NewPartialFileReader(fp)
|
||||||
|
partialFileReader.openFile = openFile
|
||||||
|
partialFileReader.openFileCache = openFileCache
|
||||||
|
reader = partialFileReader
|
||||||
|
} else {
|
||||||
|
var fileReader = NewFileReader(fp)
|
||||||
|
fileReader.openFile = openFile
|
||||||
|
fileReader.openFileCache = openFileCache
|
||||||
|
reader = fileReader
|
||||||
|
}
|
||||||
err = reader.Init()
|
err = reader.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -283,41 +359,8 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool)
|
|||||||
|
|
||||||
// 增加点击量
|
// 增加点击量
|
||||||
// 1/1000采样
|
// 1/1000采样
|
||||||
if allowMemory {
|
if !isPartial && allowMemory && reader.BodySize() < FileToMemoryMaxSize {
|
||||||
var rate = this.policy.PersistenceHitSampleRate
|
this.increaseHit(key, hash, reader)
|
||||||
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
|
isOk = true
|
||||||
@@ -325,14 +368,34 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenWriter 打开缓存文件等待写入
|
// OpenWriter 打开缓存文件等待写入
|
||||||
func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, size int64, isPartial bool) (Writer, error) {
|
func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||||
|
// 是否正在退出
|
||||||
|
if teaconst.IsQuiting {
|
||||||
|
return nil, ErrWritingUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否已忽略
|
||||||
|
if this.ignoreKeys.Has(key) {
|
||||||
|
return nil, ErrEntityTooLarge
|
||||||
|
}
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
// 我们限定仅小文件优先存在内存中
|
// 我们限定仅小文件优先存在内存中
|
||||||
if !isPartial && this.memoryStorage != nil && size > 0 && size < 32*1024*1024 {
|
var maxMemorySize = FileToMemoryMaxSize
|
||||||
writer, err := this.memoryStorage.OpenWriter(key, expiredAt, status, size, false)
|
if maxSize > maxMemorySize {
|
||||||
|
maxMemorySize = maxSize
|
||||||
|
}
|
||||||
|
var memoryStorage = this.memoryStorage
|
||||||
|
if !isPartial && memoryStorage != nil && ((size > 0 && size < maxMemorySize) || size < 0) {
|
||||||
|
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, size, maxMemorySize, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return writer, nil
|
return writer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果队列满了,则等待
|
||||||
|
if err == ErrWritingQueueFull {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否正在写入
|
// 是否正在写入
|
||||||
@@ -367,7 +430,7 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hash = stringutil.Md5(key)
|
var hash = stringutil.Md5(key)
|
||||||
var dir = this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
var dir = this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
||||||
_, err = os.Stat(dir)
|
_, err = os.Stat(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
@@ -380,7 +443,8 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查缓存是否已经生成
|
// 检查缓存是否已经生成
|
||||||
var cachePath = dir + "/" + hash + ".cache"
|
var cachePathName = dir + "/" + hash
|
||||||
|
var cachePath = cachePathName + ".cache"
|
||||||
stat, err := os.Stat(cachePath)
|
stat, err := os.Stat(cachePath)
|
||||||
if err == nil && time.Now().Sub(stat.ModTime()) <= 1*time.Second {
|
if err == nil && time.Now().Sub(stat.ModTime()) <= 1*time.Second {
|
||||||
// 防止并发连续写入
|
// 防止并发连续写入
|
||||||
@@ -388,16 +452,36 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, siz
|
|||||||
}
|
}
|
||||||
var tmpPath = cachePath + ".tmp"
|
var tmpPath = cachePath + ".tmp"
|
||||||
if isPartial {
|
if isPartial {
|
||||||
tmpPath = cachePath
|
tmpPath = cachePathName + ".cache"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先删除
|
// 先删除
|
||||||
err = this.list.Remove(hash)
|
if !isPartial {
|
||||||
if err != nil {
|
err = this.list.Remove(hash)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_SYNC|os.O_WRONLY, 0666)
|
// 从已经存储的内容中读取信息
|
||||||
|
var isNewCreated = true
|
||||||
|
var partialBodyOffset int64
|
||||||
|
if isPartial {
|
||||||
|
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
||||||
|
if err == nil {
|
||||||
|
var partialReader = NewPartialFileReader(readerFp)
|
||||||
|
err = partialReader.Init()
|
||||||
|
_ = partialReader.Close()
|
||||||
|
if err == nil && partialReader.bodyOffset > 0 {
|
||||||
|
isNewCreated = false
|
||||||
|
partialBodyOffset = partialReader.bodyOffset
|
||||||
|
} else {
|
||||||
|
_ = this.removeCacheFile(tmpPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -424,22 +508,6 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, siz
|
|||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否已经有内容
|
|
||||||
var isNewCreated = true
|
|
||||||
var partialBodyOffset int64
|
|
||||||
if isPartial {
|
|
||||||
partialFP, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
|
||||||
if err == nil {
|
|
||||||
var partialReader = NewFileReader(partialFP)
|
|
||||||
err = partialReader.InitAutoDiscard(false)
|
|
||||||
if err == nil && partialReader.bodyOffset > 0 {
|
|
||||||
isNewCreated = false
|
|
||||||
partialBodyOffset = partialReader.bodyOffset
|
|
||||||
}
|
|
||||||
_ = partialReader.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNewCreated {
|
if isNewCreated {
|
||||||
err = writer.Truncate(0)
|
err = writer.Truncate(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -502,13 +570,18 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, siz
|
|||||||
|
|
||||||
isOk = true
|
isOk = true
|
||||||
if isPartial {
|
if isPartial {
|
||||||
return NewPartialFileWriter(writer, key, expiredAt, isNewCreated, isPartial, partialBodyOffset, func() {
|
ranges, err := NewPartialRangesFromFile(cachePathName + "@ranges.cache")
|
||||||
|
if err != nil {
|
||||||
|
ranges = NewPartialRanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewPartialFileWriter(writer, key, expiredAt, isNewCreated, isPartial, partialBodyOffset, ranges, func() {
|
||||||
sharedWritingFileKeyLocker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(sharedWritingFileKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
sharedWritingFileKeyLocker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
}), nil
|
}), nil
|
||||||
} else {
|
} else {
|
||||||
return NewFileWriter(writer, key, expiredAt, func() {
|
return NewFileWriter(this, writer, key, expiredAt, -1, func() {
|
||||||
sharedWritingFileKeyLocker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(sharedWritingFileKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
sharedWritingFileKeyLocker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
@@ -518,9 +591,15 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int, siz
|
|||||||
|
|
||||||
// AddToList 添加到List
|
// AddToList 添加到List
|
||||||
func (this *FileStorage) AddToList(item *Item) {
|
func (this *FileStorage) AddToList(item *Item) {
|
||||||
if this.memoryStorage != nil {
|
// 是否正在退出
|
||||||
|
if teaconst.IsQuiting {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var memoryStorage = this.memoryStorage
|
||||||
|
if memoryStorage != nil {
|
||||||
if item.Type == ItemTypeMemory {
|
if item.Type == ItemTypeMemory {
|
||||||
this.memoryStorage.AddToList(item)
|
memoryStorage.AddToList(item)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,23 +614,29 @@ func (this *FileStorage) AddToList(item *Item) {
|
|||||||
|
|
||||||
// Delete 删除某个键值对应的缓存
|
// Delete 删除某个键值对应的缓存
|
||||||
func (this *FileStorage) Delete(key string) error {
|
func (this *FileStorage) Delete(key string) error {
|
||||||
|
// 是否正在退出
|
||||||
|
if teaconst.IsQuiting {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
if this.memoryStorage != nil {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = this.memoryStorage.Delete(key)
|
_ = memoryStorage.Delete(key)
|
||||||
}
|
})
|
||||||
|
|
||||||
hash, path := this.keyPath(key)
|
hash, path := this.keyPath(key)
|
||||||
err := this.list.Remove(hash)
|
err := this.list.Remove(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = os.Remove(path)
|
err = this.removeCacheFile(path)
|
||||||
if err == nil || os.IsNotExist(err) {
|
if err == nil || os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,9 +656,9 @@ func (this *FileStorage) CleanAll() error {
|
|||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
if this.memoryStorage != nil {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = this.memoryStorage.CleanAll()
|
_ = memoryStorage.CleanAll()
|
||||||
}
|
})
|
||||||
|
|
||||||
err := this.list.CleanAll()
|
err := this.list.CleanAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -638,13 +723,18 @@ func (this *FileStorage) CleanAll() error {
|
|||||||
|
|
||||||
// Purge 清理过期的缓存
|
// Purge 清理过期的缓存
|
||||||
func (this *FileStorage) Purge(keys []string, urlType string) error {
|
func (this *FileStorage) Purge(keys []string, urlType string) error {
|
||||||
|
// 是否正在退出
|
||||||
|
if teaconst.IsQuiting {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
if this.memoryStorage != nil {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
_ = this.memoryStorage.Purge(keys, urlType)
|
_ = memoryStorage.Purge(keys, urlType)
|
||||||
}
|
})
|
||||||
|
|
||||||
// 目录
|
// 目录
|
||||||
if urlType == "dir" {
|
if urlType == "dir" {
|
||||||
@@ -659,10 +749,11 @@ func (this *FileStorage) Purge(keys []string, urlType string) error {
|
|||||||
// 文件
|
// 文件
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
hash, path := this.keyPath(key)
|
hash, path := this.keyPath(key)
|
||||||
err := os.Remove(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = this.list.Remove(hash)
|
err = this.list.Remove(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -679,9 +770,9 @@ func (this *FileStorage) Stop() {
|
|||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
// 先尝试内存缓存
|
// 先尝试内存缓存
|
||||||
if this.memoryStorage != nil {
|
this.runMemoryStorageSafety(func(memoryStorage *MemoryStorage) {
|
||||||
this.memoryStorage.Stop()
|
memoryStorage.Stop()
|
||||||
}
|
})
|
||||||
|
|
||||||
_ = this.list.Reset()
|
_ = this.list.Reset()
|
||||||
if this.purgeTicker != nil {
|
if this.purgeTicker != nil {
|
||||||
@@ -696,6 +787,8 @@ func (this *FileStorage) Stop() {
|
|||||||
if this.openFileCache != nil {
|
if this.openFileCache != nil {
|
||||||
this.openFileCache.CloseAll()
|
this.openFileCache.CloseAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.ignoreKeys.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalDiskSize 消耗的磁盘尺寸
|
// TotalDiskSize 消耗的磁盘尺寸
|
||||||
@@ -705,21 +798,27 @@ func (this *FileStorage) TotalDiskSize() int64 {
|
|||||||
|
|
||||||
// TotalMemorySize 内存尺寸
|
// TotalMemorySize 内存尺寸
|
||||||
func (this *FileStorage) TotalMemorySize() int64 {
|
func (this *FileStorage) TotalMemorySize() int64 {
|
||||||
if this.memoryStorage == nil {
|
var memoryStorage = this.memoryStorage
|
||||||
|
if memoryStorage == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return this.memoryStorage.TotalMemorySize()
|
return memoryStorage.TotalMemorySize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||||
|
func (this *FileStorage) IgnoreKey(key string) {
|
||||||
|
this.ignoreKeys.Push(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绝对路径
|
// 绝对路径
|
||||||
func (this *FileStorage) dir() string {
|
func (this *FileStorage) dir() string {
|
||||||
return this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/"
|
return this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Key对应的文件路径
|
// 获取Key对应的文件路径
|
||||||
func (this *FileStorage) keyPath(key string) (hash string, path string) {
|
func (this *FileStorage) keyPath(key string) (hash string, path string) {
|
||||||
hash = stringutil.Md5(key)
|
hash = stringutil.Md5(key)
|
||||||
dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
||||||
path = dir + "/" + hash + ".cache"
|
path = dir + "/" + hash + ".cache"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -729,7 +828,7 @@ func (this *FileStorage) hashPath(hash string) (path string) {
|
|||||||
if len(hash) != 32 {
|
if len(hash) != 32 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
dir := this.options.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
|
||||||
path = dir + "/" + hash + ".cache"
|
path = dir + "/" + hash + ".cache"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -750,14 +849,22 @@ func (this *FileStorage) initList() error {
|
|||||||
})**/
|
})**/
|
||||||
|
|
||||||
// 启动定时清理任务
|
// 启动定时清理任务
|
||||||
var autoPurgeInterval = this.policy.PersistenceAutoPurgeInterval
|
this.initPurgeTicker()
|
||||||
if autoPurgeInterval <= 0 {
|
|
||||||
autoPurgeInterval = 30
|
// 热点处理任务
|
||||||
if Tea.IsTesting() {
|
this.hotTicker = utils.NewTicker(1 * time.Minute)
|
||||||
autoPurgeInterval = 10
|
if Tea.IsTesting() {
|
||||||
}
|
this.hotTicker = utils.NewTicker(10 * time.Second)
|
||||||
}
|
}
|
||||||
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
|
goman.New(func() {
|
||||||
|
for this.hotTicker.Next() {
|
||||||
|
trackers.Run("FILE_CACHE_STORAGE_HOT_LOOP", func() {
|
||||||
|
this.hotLoop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 退出时停止
|
||||||
events.OnKey(events.EventQuit, this, func() {
|
events.OnKey(events.EventQuit, this, func() {
|
||||||
remotelogs.Println("CACHE", "quit clean timer")
|
remotelogs.Println("CACHE", "quit clean timer")
|
||||||
|
|
||||||
@@ -774,26 +881,6 @@ func (this *FileStorage) initList() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
goman.New(func() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
goman.New(func() {
|
|
||||||
for this.hotTicker.Next() {
|
|
||||||
trackers.Run("FILE_CACHE_STORAGE_HOT_LOOP", func() {
|
|
||||||
this.hotLoop()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -837,10 +924,11 @@ func (this *FileStorage) purgeLoop() {
|
|||||||
for i := 0; i < times; i++ {
|
for i := 0; i < times; i++ {
|
||||||
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
||||||
path := this.hashPath(hash)
|
path := this.hashPath(hash)
|
||||||
err := os.Remove(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -870,10 +958,11 @@ func (this *FileStorage) purgeLoop() {
|
|||||||
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
||||||
err := this.list.PurgeLFU(count, func(hash string) error {
|
err := this.list.PurgeLFU(count, func(hash string) error {
|
||||||
path := this.hashPath(hash)
|
path := this.hashPath(hash)
|
||||||
err := os.Remove(path)
|
err := this.removeCacheFile(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -923,18 +1012,33 @@ func (this *FileStorage) hotLoop() {
|
|||||||
var buf = utils.BytePool16k.Get()
|
var buf = utils.BytePool16k.Get()
|
||||||
defer utils.BytePool16k.Put(buf)
|
defer utils.BytePool16k.Put(buf)
|
||||||
for _, item := range result[:size] {
|
for _, item := range result[:size] {
|
||||||
reader, err := this.openReader(item.Key, false, false)
|
reader, err := this.openReader(item.Key, false, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if reader == nil {
|
if reader == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if reader.ExpiresAt() <= time.Now().Unix() {
|
|
||||||
|
// 如果即将过期,则忽略
|
||||||
|
var nowUnixTime = time.Now().Unix()
|
||||||
|
if reader.ExpiresAt() <= nowUnixTime+600 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := this.memoryStorage.openWriter(item.Key, item.ExpiresAt, item.Status, reader.BodySize(), false)
|
// 计算合适的过期时间
|
||||||
|
var bestExpiresAt = nowUnixTime + HotItemLifeSeconds
|
||||||
|
var hotTimes = int64(item.Hits) / 1000
|
||||||
|
if hotTimes > 8 {
|
||||||
|
hotTimes = 8
|
||||||
|
}
|
||||||
|
bestExpiresAt += hotTimes * HotItemLifeSeconds
|
||||||
|
var expiresAt = reader.ExpiresAt()
|
||||||
|
if expiresAt <= 0 || expiresAt > bestExpiresAt {
|
||||||
|
expiresAt = bestExpiresAt
|
||||||
|
}
|
||||||
|
|
||||||
|
writer, err := memoryStorage.openWriter(item.Key, expiresAt, reader.Status(), reader.BodySize(), -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !CanIgnoreErr(err) {
|
if !CanIgnoreErr(err) {
|
||||||
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
remotelogs.Error("CACHE", "transfer hot item failed: "+err.Error())
|
||||||
@@ -970,10 +1074,10 @@ func (this *FileStorage) hotLoop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.memoryStorage.AddToList(&Item{
|
memoryStorage.AddToList(&Item{
|
||||||
Type: writer.ItemType(),
|
Type: writer.ItemType(),
|
||||||
Key: item.Key,
|
Key: item.Key,
|
||||||
ExpiredAt: item.ExpiresAt,
|
ExpiredAt: expiresAt,
|
||||||
HeaderSize: writer.HeaderSize(),
|
HeaderSize: writer.HeaderSize(),
|
||||||
BodySize: writer.BodySize(),
|
BodySize: writer.BodySize(),
|
||||||
})
|
})
|
||||||
@@ -985,9 +1089,9 @@ func (this *FileStorage) hotLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileStorage) diskCapacityBytes() int64 {
|
func (this *FileStorage) diskCapacityBytes() int64 {
|
||||||
c1 := this.policy.CapacityBytes()
|
var c1 = this.policy.CapacityBytes()
|
||||||
if SharedManager.MaxDiskCapacity != nil {
|
if SharedManager.MaxDiskCapacity != nil {
|
||||||
c2 := SharedManager.MaxDiskCapacity.Bytes()
|
var c2 = SharedManager.MaxDiskCapacity.Bytes()
|
||||||
if c2 > 0 {
|
if c2 > 0 {
|
||||||
return c2
|
return c2
|
||||||
}
|
}
|
||||||
@@ -1025,3 +1129,141 @@ func (this *FileStorage) cleanDeletedDirs(dir string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 增加某个Key的点击量
|
||||||
|
func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||||
|
var rate = this.policy.PersistenceHitSampleRate
|
||||||
|
if rate <= 0 {
|
||||||
|
rate = 1000
|
||||||
|
}
|
||||||
|
if this.lastHotSize == 0 {
|
||||||
|
// 自动降低采样率来增加热点数据的缓存几率
|
||||||
|
rate = rate / 10
|
||||||
|
}
|
||||||
|
if rands.Int(0, rate) == 0 {
|
||||||
|
var memoryStorage = this.memoryStorage
|
||||||
|
|
||||||
|
var hitErr = this.list.IncreaseHit(hash)
|
||||||
|
if hitErr != nil {
|
||||||
|
// 此错误可以忽略
|
||||||
|
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增加到热点
|
||||||
|
// 这里不收录缓存尺寸过大的文件
|
||||||
|
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*1024*1024 {
|
||||||
|
this.hotMapLocker.Lock()
|
||||||
|
hotItem, ok := this.hotMap[key]
|
||||||
|
|
||||||
|
// 限制热点数据存活时间
|
||||||
|
var unixTime = time.Now().Unix()
|
||||||
|
var expiresAt = reader.ExpiresAt()
|
||||||
|
if expiresAt <= 0 || expiresAt > unixTime+HotItemLifeSeconds {
|
||||||
|
expiresAt = unixTime + HotItemLifeSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
hotItem.Hits++
|
||||||
|
} else if len(this.hotMap) < HotItemSize { // 控制数量
|
||||||
|
this.hotMap[key] = &HotItem{
|
||||||
|
Key: key,
|
||||||
|
Hits: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.hotMapLocker.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除缓存文件
|
||||||
|
func (this *FileStorage) removeCacheFile(path string) error {
|
||||||
|
var err = os.Remove(path)
|
||||||
|
if err == nil || os.IsNotExist(err) {
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
// 删除Partial相关
|
||||||
|
_ = os.Remove(partialRangesFilePath(path))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建当前策略包含的内存缓存
|
||||||
|
func (this *FileStorage) createMemoryStorage() error {
|
||||||
|
var memoryPolicy = &serverconfigs.HTTPCachePolicy{
|
||||||
|
Id: this.policy.Id,
|
||||||
|
IsOn: this.policy.IsOn,
|
||||||
|
Name: this.policy.Name,
|
||||||
|
Description: this.policy.Description,
|
||||||
|
Capacity: this.options.MemoryPolicy.Capacity,
|
||||||
|
MaxKeys: this.policy.MaxKeys,
|
||||||
|
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
||||||
|
Type: serverconfigs.CachePolicyStorageMemory,
|
||||||
|
Options: this.policy.Options,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
var memoryStorage = NewMemoryStorage(memoryPolicy, this)
|
||||||
|
err = memoryStorage.Init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
this.memoryStorage = memoryStorage
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileStorage) initPurgeTicker() {
|
||||||
|
var autoPurgeInterval = this.policy.PersistenceAutoPurgeInterval
|
||||||
|
if autoPurgeInterval <= 0 {
|
||||||
|
autoPurgeInterval = 30
|
||||||
|
if Tea.IsTesting() {
|
||||||
|
autoPurgeInterval = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if this.purgeTicker != nil {
|
||||||
|
this.purgeTicker.Stop()
|
||||||
|
}
|
||||||
|
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
|
||||||
|
goman.New(func() {
|
||||||
|
for this.purgeTicker.Next() {
|
||||||
|
trackers.Run("FILE_CACHE_STORAGE_PURGE_LOOP", func() {
|
||||||
|
this.purgeLoop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileStorage) initOpenFileCache() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var oldOpenFileCache = this.openFileCache
|
||||||
|
|
||||||
|
// 启用新的
|
||||||
|
if this.options.OpenFileCache != nil && this.options.OpenFileCache.IsOn && this.options.OpenFileCache.Max > 0 {
|
||||||
|
this.openFileCache, err = NewOpenFileCache(this.options.OpenFileCache.Max)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("CACHE", "open file cache failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭老的
|
||||||
|
if oldOpenFileCache != nil {
|
||||||
|
oldOpenFileCache.CloseAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStorage)) {
|
||||||
|
var memoryStorage = this.memoryStorage
|
||||||
|
if memoryStorage != nil {
|
||||||
|
f(memoryStorage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestFileStorage_OpenWriter(t *testing.T) {
|
|||||||
|
|
||||||
header := []byte("Header")
|
header := []byte("Header")
|
||||||
body := []byte("This is Body")
|
body := []byte("This is Body")
|
||||||
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, false)
|
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, true)
|
writer, err := storage.OpenWriter("my-key", time.Now().Unix()+86400, 200, -1, -1, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.WriteAt([]byte("Hello, World"), 0)
|
err = writer.WriteAt(0, []byte("Hello, World"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
|||||||
t.Log(time.Since(now).Seconds()*1000, "ms")
|
t.Log(time.Since(now).Seconds()*1000, "ms")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, false)
|
writer, err := storage.OpenWriter("my-http-response", time.Now().Unix()+86400, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
|||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, false)
|
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrFileIsWriting {
|
if err != ErrFileIsWriting {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -267,7 +267,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
|||||||
go func(i int) {
|
go func(i int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, false)
|
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != ErrFileIsWriting {
|
if err != ErrFileIsWriting {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -311,7 +311,7 @@ func TestFileStorage_Read(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
reader, err := storage.OpenReader("my-key", false)
|
reader, err := storage.OpenReader("my-key", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -347,7 +347,7 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
reader, err := storage.OpenReader("my-http-response", false)
|
reader, err := storage.OpenReader("my-http-response", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -401,7 +401,7 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
buf := make([]byte, 6)
|
buf := make([]byte, 6)
|
||||||
reader, err := storage.OpenReader("my-key-10000", false)
|
reader, err := storage.OpenReader("my-key-10000", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("cache not fund")
|
t.Log("cache not fund")
|
||||||
@@ -511,7 +511,7 @@ func TestFileStorage_Stop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStorage_DecodeFile(t *testing.T) {
|
func TestFileStorage_DecodeFile(t *testing.T) {
|
||||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
Options: map[string]interface{}{
|
Options: map[string]interface{}{
|
||||||
@@ -526,6 +526,11 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
|||||||
t.Log(path)
|
t.Log(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||||
|
var storage = NewFileStorage(nil)
|
||||||
|
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkFileStorage_Read(b *testing.B) {
|
func BenchmarkFileStorage_Read(b *testing.B) {
|
||||||
runtime.GOMAXPROCS(1)
|
runtime.GOMAXPROCS(1)
|
||||||
|
|
||||||
@@ -543,7 +548,7 @@ func BenchmarkFileStorage_Read(b *testing.B) {
|
|||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
reader, err := storage.OpenReader("my-key", false)
|
reader, err := storage.OpenReader("my-key", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -559,8 +564,8 @@ func BenchmarkFileStorage_KeyPath(b *testing.B) {
|
|||||||
runtime.GOMAXPROCS(1)
|
runtime.GOMAXPROCS(1)
|
||||||
|
|
||||||
var storage = &FileStorage{
|
var storage = &FileStorage{
|
||||||
cacheConfig: &serverconfigs.HTTPFileCacheStorage{},
|
options: &serverconfigs.HTTPFileCacheStorage{},
|
||||||
policy: &serverconfigs.HTTPCachePolicy{Id: 1},
|
policy: &serverconfigs.HTTPCachePolicy{Id: 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ type StorageInterface interface {
|
|||||||
Init() error
|
Init() error
|
||||||
|
|
||||||
// OpenReader 读取缓存
|
// OpenReader 读取缓存
|
||||||
OpenReader(key string, useStale bool) (reader Reader, err error)
|
OpenReader(key string, useStale bool, isPartial bool) (reader Reader, err error)
|
||||||
|
|
||||||
// OpenWriter 打开缓存写入器等待写入
|
// OpenWriter 打开缓存写入器等待写入
|
||||||
OpenWriter(key string, expiredAt int64, status int, size int64, isPartial bool) (Writer, error)
|
// size 和 maxSize 可能为-1
|
||||||
|
OpenWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error)
|
||||||
|
|
||||||
// Delete 删除某个键值对应的缓存
|
// Delete 删除某个键值对应的缓存
|
||||||
Delete(key string) error
|
Delete(key string) error
|
||||||
@@ -39,6 +40,15 @@ type StorageInterface interface {
|
|||||||
// Policy 获取当前存储的Policy
|
// Policy 获取当前存储的Policy
|
||||||
Policy() *serverconfigs.HTTPCachePolicy
|
Policy() *serverconfigs.HTTPCachePolicy
|
||||||
|
|
||||||
|
// UpdatePolicy 修改策略
|
||||||
|
UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||||
|
|
||||||
|
// CanUpdatePolicy 检查策略是否可以更新
|
||||||
|
CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool
|
||||||
|
|
||||||
// AddToList 将缓存添加到列表
|
// AddToList 将缓存添加到列表
|
||||||
AddToList(item *Item)
|
AddToList(item *Item)
|
||||||
|
|
||||||
|
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||||
|
IgnoreKey(key string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"github.com/shirou/gopsutil/v3/load"
|
||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -40,31 +43,39 @@ type MemoryStorage struct {
|
|||||||
locker *sync.RWMutex
|
locker *sync.RWMutex
|
||||||
|
|
||||||
valuesMap map[uint64]*MemoryItem // hash => item
|
valuesMap map[uint64]*MemoryItem // hash => item
|
||||||
dirtyChan chan string // hash chan
|
|
||||||
|
dirtyChan chan string // hash chan
|
||||||
|
dirtyQueueSize int
|
||||||
|
|
||||||
purgeTicker *utils.Ticker
|
purgeTicker *utils.Ticker
|
||||||
|
|
||||||
totalSize int64
|
totalSize int64
|
||||||
writingKeyMap map[string]zero.Zero // key => bool
|
writingKeyMap map[string]zero.Zero // key => bool
|
||||||
|
|
||||||
|
ignoreKeys *setutils.FixedSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage StorageInterface) *MemoryStorage {
|
func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage StorageInterface) *MemoryStorage {
|
||||||
var dirtyChan chan string
|
var dirtyChan chan string
|
||||||
|
var queueSize = policy.MemoryAutoFlushQueueSize
|
||||||
|
|
||||||
if parentStorage != nil {
|
if parentStorage != nil {
|
||||||
var queueSize = policy.MemoryAutoFlushQueueSize
|
|
||||||
if queueSize <= 0 {
|
if queueSize <= 0 {
|
||||||
queueSize = 2048
|
queueSize = 2048 + int(policy.CapacityBytes()/sizes.G)*2048
|
||||||
}
|
}
|
||||||
|
|
||||||
dirtyChan = make(chan string, queueSize)
|
dirtyChan = make(chan string, queueSize)
|
||||||
}
|
}
|
||||||
return &MemoryStorage{
|
return &MemoryStorage{
|
||||||
parentStorage: parentStorage,
|
parentStorage: parentStorage,
|
||||||
policy: policy,
|
policy: policy,
|
||||||
list: NewMemoryList(),
|
list: NewMemoryList(),
|
||||||
locker: &sync.RWMutex{},
|
locker: &sync.RWMutex{},
|
||||||
valuesMap: map[uint64]*MemoryItem{},
|
valuesMap: map[uint64]*MemoryItem{},
|
||||||
dirtyChan: dirtyChan,
|
dirtyChan: dirtyChan,
|
||||||
writingKeyMap: map[string]zero.Zero{},
|
dirtyQueueSize: queueSize,
|
||||||
|
writingKeyMap: map[string]zero.Zero{},
|
||||||
|
ignoreKeys: setutils.NewFixedSet(32768),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,33 +90,25 @@ func (this *MemoryStorage) Init() error {
|
|||||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
||||||
})
|
})
|
||||||
|
|
||||||
var autoPurgeInterval = this.policy.MemoryAutoPurgeInterval
|
this.initPurgeTicker()
|
||||||
if autoPurgeInterval <= 0 {
|
|
||||||
autoPurgeInterval = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动定时清理任务
|
|
||||||
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
|
|
||||||
goman.New(func() {
|
|
||||||
for this.purgeTicker.Next() {
|
|
||||||
var tr = trackers.Begin("MEMORY_CACHE_STORAGE_PURGE_LOOP")
|
|
||||||
this.purgeLoop()
|
|
||||||
tr.End()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 启动定时Flush memory to disk任务
|
// 启动定时Flush memory to disk任务
|
||||||
goman.New(func() {
|
if this.parentStorage != nil {
|
||||||
for hash := range this.dirtyChan {
|
// TODO 应该根据磁盘性能决定线程数
|
||||||
this.flushItem(hash)
|
var threads = 1
|
||||||
|
|
||||||
|
for i := 0; i < threads; i++ {
|
||||||
|
goman.New(func() {
|
||||||
|
this.startFlush()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenReader 读取缓存
|
// OpenReader 读取缓存
|
||||||
func (this *MemoryStorage) OpenReader(key string, useStale bool) (Reader, error) {
|
func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
|
||||||
hash := this.hash(key)
|
hash := this.hash(key)
|
||||||
|
|
||||||
this.locker.RLock()
|
this.locker.RLock()
|
||||||
@@ -145,15 +148,27 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool) (Reader, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenWriter 打开缓存写入器等待写入
|
// OpenWriter 打开缓存写入器等待写入
|
||||||
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, size int64, isPartial bool) (Writer, error) {
|
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||||
|
if this.ignoreKeys.Has(key) {
|
||||||
|
return nil, ErrEntityTooLarge
|
||||||
|
}
|
||||||
|
|
||||||
// TODO 内存缓存暂时不支持分块内容存储
|
// TODO 内存缓存暂时不支持分块内容存储
|
||||||
if isPartial {
|
if isPartial {
|
||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
return this.openWriter(key, expiredAt, status, size, true)
|
return this.openWriter(key, expiredAt, status, size, maxSize, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, size int64, isDirty bool) (Writer, error) {
|
func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, size int64, maxSize int64, isDirty bool) (Writer, error) {
|
||||||
|
// 待写入队列是否已满
|
||||||
|
if isDirty &&
|
||||||
|
this.parentStorage != nil &&
|
||||||
|
this.dirtyQueueSize > 0 &&
|
||||||
|
len(this.dirtyChan) == this.dirtyQueueSize { // 缓存时间过长
|
||||||
|
return nil, ErrWritingQueueFull
|
||||||
|
}
|
||||||
|
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
defer this.locker.Unlock()
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
@@ -200,7 +215,7 @@ func (this *MemoryStorage) openWriter(key string, expiredAt int64, status int, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
isWriting = true
|
isWriting = true
|
||||||
return NewMemoryWriter(this, key, expiredAt, status, isDirty, func() {
|
return NewMemoryWriter(this, key, expiredAt, status, isDirty, maxSize, func() {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
delete(this.writingKeyMap, key)
|
delete(this.writingKeyMap, key)
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
@@ -269,7 +284,7 @@ func (this *MemoryStorage) Stop() {
|
|||||||
this.purgeTicker.Stop()
|
this.purgeTicker.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.parentStorage != nil && this.dirtyChan != nil {
|
if this.dirtyChan != nil {
|
||||||
close(this.dirtyChan)
|
close(this.dirtyChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +292,8 @@ func (this *MemoryStorage) Stop() {
|
|||||||
|
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
|
|
||||||
|
this.ignoreKeys.Reset()
|
||||||
|
|
||||||
// 回收内存
|
// 回收内存
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
||||||
@@ -288,6 +305,26 @@ func (this *MemoryStorage) Policy() *serverconfigs.HTTPCachePolicy {
|
|||||||
return this.policy
|
return this.policy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePolicy 修改策略
|
||||||
|
func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) {
|
||||||
|
var oldPolicy = this.policy
|
||||||
|
this.policy = newPolicy
|
||||||
|
|
||||||
|
if oldPolicy.MemoryAutoPurgeInterval != newPolicy.MemoryAutoPurgeInterval {
|
||||||
|
this.initPurgeTicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是空的,则清空
|
||||||
|
if newPolicy.CapacityBytes() == 0 {
|
||||||
|
_ = this.CleanAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanUpdatePolicy 检查策略是否可以更新
|
||||||
|
func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// AddToList 将缓存添加到列表
|
// AddToList 将缓存添加到列表
|
||||||
func (this *MemoryStorage) AddToList(item *Item) {
|
func (this *MemoryStorage) AddToList(item *Item) {
|
||||||
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
||||||
@@ -305,6 +342,11 @@ func (this *MemoryStorage) TotalMemorySize() int64 {
|
|||||||
return atomic.LoadInt64(&this.totalSize)
|
return atomic.LoadInt64(&this.totalSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||||
|
func (this *MemoryStorage) IgnoreKey(key string) {
|
||||||
|
this.ignoreKeys.Push(key)
|
||||||
|
}
|
||||||
|
|
||||||
// 计算Key Hash
|
// 计算Key Hash
|
||||||
func (this *MemoryStorage) hash(key string) uint64 {
|
func (this *MemoryStorage) hash(key string) uint64 {
|
||||||
return xxhash.Sum64String(key)
|
return xxhash.Sum64String(key)
|
||||||
@@ -373,7 +415,40 @@ func (this *MemoryStorage) purgeLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush任务
|
// 开始Flush任务
|
||||||
|
func (this *MemoryStorage) startFlush() {
|
||||||
|
var statCount = 0
|
||||||
|
var writeDelayMS float64 = 0
|
||||||
|
|
||||||
|
for hash := range this.dirtyChan {
|
||||||
|
statCount++
|
||||||
|
|
||||||
|
if statCount == 100 {
|
||||||
|
statCount = 0
|
||||||
|
|
||||||
|
loadStat, err := load.Avg()
|
||||||
|
if err == nil && loadStat != nil {
|
||||||
|
if loadStat.Load1 > 10 {
|
||||||
|
writeDelayMS = 100
|
||||||
|
} else if loadStat.Load1 > 3 {
|
||||||
|
writeDelayMS = 50
|
||||||
|
} else if loadStat.Load1 > 2 {
|
||||||
|
writeDelayMS = 10
|
||||||
|
} else {
|
||||||
|
writeDelayMS = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flushItem(hash)
|
||||||
|
|
||||||
|
if writeDelayMS > 0 {
|
||||||
|
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单次Flush任务
|
||||||
func (this *MemoryStorage) flushItem(key string) {
|
func (this *MemoryStorage) flushItem(key string) {
|
||||||
if this.parentStorage == nil {
|
if this.parentStorage == nil {
|
||||||
return
|
return
|
||||||
@@ -391,7 +466,7 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := this.parentStorage.OpenWriter(key, item.ExpiredAt, item.Status, -1, false)
|
writer, err := this.parentStorage.OpenWriter(key, item.ExpiredAt, item.Status, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !CanIgnoreErr(err) {
|
if !CanIgnoreErr(err) {
|
||||||
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
remotelogs.Error("CACHE", "flush items failed: open writer failed: "+err.Error())
|
||||||
@@ -417,6 +492,7 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
_ = writer.Discard()
|
_ = writer.Discard()
|
||||||
remotelogs.Error("CACHE", "flush items failed: close writer failed: "+err.Error())
|
remotelogs.Error("CACHE", "flush items failed: close writer failed: "+err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.parentStorage.AddToList(&Item{
|
this.parentStorage.AddToList(&Item{
|
||||||
@@ -427,6 +503,9 @@ func (this *MemoryStorage) flushItem(key string) {
|
|||||||
BodySize: writer.BodySize(),
|
BodySize: writer.BodySize(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 从内存中移除
|
||||||
|
_ = this.Delete(key)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,3 +532,25 @@ func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
|||||||
_ = this.list.Remove(fmt.Sprintf("%d", hash))
|
_ = this.list.Remove(fmt.Sprintf("%d", hash))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *MemoryStorage) initPurgeTicker() {
|
||||||
|
var autoPurgeInterval = this.policy.MemoryAutoPurgeInterval
|
||||||
|
if autoPurgeInterval <= 0 {
|
||||||
|
autoPurgeInterval = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动定时清理任务
|
||||||
|
|
||||||
|
if this.purgeTicker != nil {
|
||||||
|
this.purgeTicker.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.purgeTicker = utils.NewTicker(time.Duration(autoPurgeInterval) * time.Second)
|
||||||
|
goman.New(func() {
|
||||||
|
for this.purgeTicker.Next() {
|
||||||
|
var tr = trackers.Begin("MEMORY_CACHE_STORAGE_PURGE_LOOP")
|
||||||
|
this.purgeLoop()
|
||||||
|
tr.End()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
func TestMemoryStorage_OpenWriter(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
|
|
||||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, false)
|
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
t.Log(storage.valuesMap)
|
t.Log(storage.valuesMap)
|
||||||
|
|
||||||
{
|
{
|
||||||
reader, err := storage.OpenReader("abc", false)
|
reader, err := storage.OpenReader("abc", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc")
|
t.Log("not found: abc")
|
||||||
@@ -52,7 +52,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
_, err := storage.OpenReader("abc 2", false)
|
_, err := storage.OpenReader("abc 2", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc2")
|
t.Log("not found: abc2")
|
||||||
@@ -62,13 +62,13 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, false)
|
writer, err = storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello123"))
|
_, _ = writer.Write([]byte("Hello123"))
|
||||||
{
|
{
|
||||||
reader, err := storage.OpenReader("abc", false)
|
reader, err := storage.OpenReader("abc", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc")
|
t.Log("not found: abc")
|
||||||
@@ -97,13 +97,13 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
|
|||||||
IsDone: true,
|
IsDone: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, _ = storage.OpenReader("test", false)
|
_, _ = storage.OpenReader("test", false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Delete(t *testing.T) {
|
func TestMemoryStorage_Delete(t *testing.T) {
|
||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, false)
|
writer, err := storage.OpenWriter("abc", time.Now().Unix()+60, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ func TestMemoryStorage_Delete(t *testing.T) {
|
|||||||
t.Log(len(storage.valuesMap))
|
t.Log(len(storage.valuesMap))
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, false)
|
writer, err := storage.OpenWriter("abc1", time.Now().Unix()+60, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
|||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
|||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
storage := NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||||
expiredAt := time.Now().Unix() + 60
|
expiredAt := time.Now().Unix() + 60
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter("abc", expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter("abc1", expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
|||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
|
expiredAt := time.Now().Unix() + int64(rands.Int(0, 60))
|
||||||
key := "abc" + strconv.Itoa(i)
|
key := "abc" + strconv.Itoa(i)
|
||||||
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, false)
|
writer, err := storage.OpenWriter(key, expiredAt, 200, -1, -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
18
internal/caches/utils_partial.go
Normal file
18
internal/caches/utils_partial.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package caches
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// 获取 ranges 文件路径
|
||||||
|
func partialRangesFilePath(path string) string {
|
||||||
|
// ranges路径
|
||||||
|
var dotIndex = strings.LastIndex(path, ".")
|
||||||
|
var rangePath = ""
|
||||||
|
if dotIndex < 0 {
|
||||||
|
rangePath = path + "@ranges.cache"
|
||||||
|
} else {
|
||||||
|
rangePath = path[:dotIndex] + "@ranges" + path[dotIndex:]
|
||||||
|
}
|
||||||
|
return rangePath
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ type Writer interface {
|
|||||||
Write(data []byte) (n int, err error)
|
Write(data []byte) (n int, err error)
|
||||||
|
|
||||||
// WriteAt 在指定位置写入数据
|
// WriteAt 在指定位置写入数据
|
||||||
WriteAt(data []byte, offset int64) error
|
WriteAt(offset int64, data []byte) error
|
||||||
|
|
||||||
// HeaderSize 写入的Header数据大小
|
// HeaderSize 写入的Header数据大小
|
||||||
HeaderSize() int64
|
HeaderSize() int64
|
||||||
|
|||||||
@@ -11,20 +11,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FileWriter struct {
|
type FileWriter struct {
|
||||||
|
storage StorageInterface
|
||||||
rawWriter *os.File
|
rawWriter *os.File
|
||||||
key string
|
key string
|
||||||
headerSize int64
|
headerSize int64
|
||||||
bodySize int64
|
bodySize int64
|
||||||
expiredAt int64
|
expiredAt int64
|
||||||
|
maxSize int64
|
||||||
endFunc func()
|
endFunc func()
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileWriter(rawWriter *os.File, key string, expiredAt int64, endFunc func()) *FileWriter {
|
func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, expiredAt int64, maxSize int64, endFunc func()) *FileWriter {
|
||||||
return &FileWriter{
|
return &FileWriter{
|
||||||
|
storage: storage,
|
||||||
key: key,
|
key: key,
|
||||||
rawWriter: rawWriter,
|
rawWriter: rawWriter,
|
||||||
expiredAt: expiredAt,
|
expiredAt: expiredAt,
|
||||||
|
maxSize: maxSize,
|
||||||
endFunc: endFunc,
|
endFunc: endFunc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +64,15 @@ func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
|||||||
func (this *FileWriter) Write(data []byte) (n int, err error) {
|
func (this *FileWriter) Write(data []byte) (n int, err error) {
|
||||||
n, err = this.rawWriter.Write(data)
|
n, err = this.rawWriter.Write(data)
|
||||||
this.bodySize += int64(n)
|
this.bodySize += int64(n)
|
||||||
|
|
||||||
|
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||||
|
err = ErrEntityTooLarge
|
||||||
|
|
||||||
|
if this.storage != nil {
|
||||||
|
this.storage.IgnoreKey(this.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = this.Discard()
|
_ = this.Discard()
|
||||||
}
|
}
|
||||||
@@ -67,7 +80,7 @@ func (this *FileWriter) Write(data []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteAt 在指定位置写入数据
|
// WriteAt 在指定位置写入数据
|
||||||
func (this *FileWriter) WriteAt(data []byte, offset int64) error {
|
func (this *FileWriter) WriteAt(offset int64, data []byte) error {
|
||||||
_ = data
|
_ = data
|
||||||
_ = offset
|
_ = offset
|
||||||
return errors.New("not supported")
|
return errors.New("not supported")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type MemoryWriter struct {
|
|||||||
bodySize int64
|
bodySize int64
|
||||||
status int
|
status int
|
||||||
isDirty bool
|
isDirty bool
|
||||||
|
maxSize int64
|
||||||
|
|
||||||
hash uint64
|
hash uint64
|
||||||
item *MemoryItem
|
item *MemoryItem
|
||||||
@@ -23,7 +24,7 @@ type MemoryWriter struct {
|
|||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, endFunc func()) *MemoryWriter {
|
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, maxSize int64, endFunc func()) *MemoryWriter {
|
||||||
w := &MemoryWriter{
|
w := &MemoryWriter{
|
||||||
storage: memoryStorage,
|
storage: memoryStorage,
|
||||||
key: key,
|
key: key,
|
||||||
@@ -35,6 +36,7 @@ func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64,
|
|||||||
},
|
},
|
||||||
status: status,
|
status: status,
|
||||||
isDirty: isDirty,
|
isDirty: isDirty,
|
||||||
|
maxSize: maxSize,
|
||||||
endFunc: endFunc,
|
endFunc: endFunc,
|
||||||
}
|
}
|
||||||
w.hash = w.calculateHash(key)
|
w.hash = w.calculateHash(key)
|
||||||
@@ -53,11 +55,19 @@ func (this *MemoryWriter) WriteHeader(data []byte) (n int, err error) {
|
|||||||
func (this *MemoryWriter) Write(data []byte) (n int, err error) {
|
func (this *MemoryWriter) Write(data []byte) (n int, err error) {
|
||||||
this.bodySize += int64(len(data))
|
this.bodySize += int64(len(data))
|
||||||
this.item.BodyValue = append(this.item.BodyValue, data...)
|
this.item.BodyValue = append(this.item.BodyValue, data...)
|
||||||
|
|
||||||
|
// 检查尺寸
|
||||||
|
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||||
|
err = ErrEntityTooLarge
|
||||||
|
this.storage.IgnoreKey(this.key)
|
||||||
|
return len(data), err
|
||||||
|
}
|
||||||
|
|
||||||
return len(data), nil
|
return len(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAt 在指定位置写入数据
|
// WriteAt 在指定位置写入数据
|
||||||
func (this *MemoryWriter) WriteAt(b []byte, offset int64) error {
|
func (this *MemoryWriter) WriteAt(offset int64, b []byte) error {
|
||||||
_ = b
|
_ = b
|
||||||
_ = offset
|
_ = offset
|
||||||
return errors.New("not supported")
|
return errors.New("not supported")
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,9 +22,12 @@ type PartialFileWriter struct {
|
|||||||
isNew bool
|
isNew bool
|
||||||
isPartial bool
|
isPartial bool
|
||||||
bodyOffset int64
|
bodyOffset int64
|
||||||
|
|
||||||
|
ranges *PartialRanges
|
||||||
|
rangePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew bool, isPartial bool, bodyOffset int64, endFunc func()) *PartialFileWriter {
|
func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew bool, isPartial bool, bodyOffset int64, ranges *PartialRanges, endFunc func()) *PartialFileWriter {
|
||||||
return &PartialFileWriter{
|
return &PartialFileWriter{
|
||||||
key: key,
|
key: key,
|
||||||
rawWriter: rawWriter,
|
rawWriter: rawWriter,
|
||||||
@@ -34,6 +36,8 @@ func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, isNew
|
|||||||
isNew: isNew,
|
isNew: isNew,
|
||||||
isPartial: isPartial,
|
isPartial: isPartial,
|
||||||
bodyOffset: bodyOffset,
|
bodyOffset: bodyOffset,
|
||||||
|
ranges: ranges,
|
||||||
|
rangePath: partialRangesFilePath(rawWriter.Name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +54,21 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileWriter) AppendHeader(data []byte) error {
|
||||||
|
_, err := this.rawWriter.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
_ = this.Discard()
|
||||||
|
} else {
|
||||||
|
var c = len(data)
|
||||||
|
this.headerSize += int64(c)
|
||||||
|
err = this.WriteHeaderLength(int(this.headerSize))
|
||||||
|
if err != nil {
|
||||||
|
_ = this.Discard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// WriteHeaderLength 写入Header长度数据
|
// WriteHeaderLength 写入Header长度数据
|
||||||
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
||||||
bytes4 := make([]byte, 4)
|
bytes4 := make([]byte, 4)
|
||||||
@@ -78,12 +97,34 @@ func (this *PartialFileWriter) Write(data []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteAt 在指定位置写入数据
|
// WriteAt 在指定位置写入数据
|
||||||
func (this *PartialFileWriter) WriteAt(data []byte, offset int64) error {
|
func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
||||||
|
var c = int64(len(data))
|
||||||
|
if c == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var end = offset + c - 1
|
||||||
|
|
||||||
|
// 是否已包含在内
|
||||||
|
if this.ranges.Contains(offset, end) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if this.bodyOffset == 0 {
|
if this.bodyOffset == 0 {
|
||||||
this.bodyOffset = SizeMeta + int64(len(this.key)) + this.headerSize
|
this.bodyOffset = SizeMeta + int64(len(this.key)) + this.headerSize
|
||||||
}
|
}
|
||||||
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ranges.Add(offset, end)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBodyLength 设置内容总长度
|
||||||
|
func (this *PartialFileWriter) SetBodyLength(bodyLength int64) {
|
||||||
|
this.bodySize = bodyLength
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBodyLength 写入Body长度数据
|
// WriteBodyLength 写入Body长度数据
|
||||||
@@ -109,31 +150,30 @@ func (this *PartialFileWriter) Close() error {
|
|||||||
this.endFunc()
|
this.endFunc()
|
||||||
})
|
})
|
||||||
|
|
||||||
var path = this.rawWriter.Name()
|
err := this.ranges.WriteToFile(this.rangePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭当前writer
|
||||||
if this.isNew {
|
if this.isNew {
|
||||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
err = this.WriteHeaderLength(types.Int(this.headerSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = this.rawWriter.Close()
|
_ = this.rawWriter.Close()
|
||||||
_ = os.Remove(path)
|
this.remove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = this.WriteBodyLength(this.bodySize)
|
err = this.WriteBodyLength(this.bodySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = this.rawWriter.Close()
|
_ = this.rawWriter.Close()
|
||||||
_ = os.Remove(path)
|
this.remove()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := this.rawWriter.Close()
|
err = this.rawWriter.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(path)
|
this.remove()
|
||||||
} else if !this.isPartial {
|
|
||||||
err = os.Rename(path, strings.Replace(path, ".tmp", "", 1))
|
|
||||||
if err != nil {
|
|
||||||
_ = os.Remove(path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -147,6 +187,8 @@ func (this *PartialFileWriter) Discard() error {
|
|||||||
|
|
||||||
_ = this.rawWriter.Close()
|
_ = this.rawWriter.Close()
|
||||||
|
|
||||||
|
_ = os.Remove(this.rangePath)
|
||||||
|
|
||||||
err := os.Remove(this.rawWriter.Name())
|
err := os.Remove(this.rawWriter.Name())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -171,3 +213,12 @@ func (this *PartialFileWriter) Key() string {
|
|||||||
func (this *PartialFileWriter) ItemType() ItemType {
|
func (this *PartialFileWriter) ItemType() ItemType {
|
||||||
return ItemTypeFile
|
return ItemTypeFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileWriter) IsNew() bool {
|
||||||
|
return this.isNew && len(this.ranges.Ranges) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PartialFileWriter) remove() {
|
||||||
|
_ = os.Remove(this.rawWriter.Name())
|
||||||
|
_ = os.Remove(this.rangePath)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPartialFileWriter_SeekOffset(t *testing.T) {
|
func TestPartialFileWriter_Write(t *testing.T) {
|
||||||
var path = "/tmp/test@partial.cache"
|
var path = "/tmp/test_partial.cache"
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
|
|
||||||
var reader = func() {
|
var reader = func() {
|
||||||
@@ -27,7 +27,8 @@ func TestPartialFileWriter_SeekOffset(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, true, true, 0, func() {
|
var ranges = caches.NewPartialRanges()
|
||||||
|
var writer = caches.NewPartialFileWriter(fp, "test", time.Now().Unix()+86500, true, true, 0, ranges, func() {
|
||||||
t.Log("end")
|
t.Log("end")
|
||||||
})
|
})
|
||||||
_, err = writer.WriteHeader([]byte("header"))
|
_, err = writer.WriteHeader([]byte("header"))
|
||||||
@@ -36,7 +37,7 @@ func TestPartialFileWriter_SeekOffset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 移动位置
|
// 移动位置
|
||||||
err = writer.WriteAt([]byte("HELLO"), 100)
|
err = writer.WriteAt(100, []byte("HELLO"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
package compressions
|
package compressions
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
type Reader interface {
|
type Reader interface {
|
||||||
Read(p []byte) (n int, err error)
|
Read(p []byte) (n int, err error)
|
||||||
|
Reset(reader io.Reader) error
|
||||||
|
RawClose() error
|
||||||
Close() error
|
Close() error
|
||||||
|
|
||||||
|
SetPool(pool *ReaderPool)
|
||||||
|
ResetFinish()
|
||||||
}
|
}
|
||||||
|
|||||||
26
internal/compressions/reader_base.go
Normal file
26
internal/compressions/reader_base.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
type BaseReader struct {
|
||||||
|
pool *ReaderPool
|
||||||
|
|
||||||
|
isFinished bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseReader) SetPool(pool *ReaderPool) {
|
||||||
|
this.pool = pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseReader) Finish(obj Reader) error {
|
||||||
|
err := obj.RawClose()
|
||||||
|
if err == nil && this.pool != nil && !this.isFinished {
|
||||||
|
this.pool.Put(obj)
|
||||||
|
}
|
||||||
|
this.isFinished = true
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseReader) ResetFinish() {
|
||||||
|
this.isFinished = false
|
||||||
|
}
|
||||||
@@ -9,10 +9,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BrotliReader struct {
|
type BrotliReader struct {
|
||||||
|
BaseReader
|
||||||
|
|
||||||
reader *brotli.Reader
|
reader *brotli.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBrotliReader(reader io.Reader) (Reader, error) {
|
func NewBrotliReader(reader io.Reader) (Reader, error) {
|
||||||
|
return sharedBrotliReaderPool.Get(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBrotliReader(reader io.Reader) (Reader, error) {
|
||||||
return &BrotliReader{reader: brotli.NewReader(reader)}, nil
|
return &BrotliReader{reader: brotli.NewReader(reader)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +30,14 @@ func (this *BrotliReader) Read(p []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *BrotliReader) Close() error {
|
func (this *BrotliReader) Reset(reader io.Reader) error {
|
||||||
|
return this.reader.Reset(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BrotliReader) RawClose() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *BrotliReader) Close() error {
|
||||||
|
return this.Finish(this)
|
||||||
|
}
|
||||||
|
|||||||
51
internal/compressions/reader_brotli_test.go
Normal file
51
internal/compressions/reader_brotli_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBrotliReader(t *testing.T) {
|
||||||
|
for _, testString := range []string{"Hello", "World", "Ni", "Hao"} {
|
||||||
|
t.Log("===", testString, "===")
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(testString))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := compressions.NewBrotliReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var data = make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(data)
|
||||||
|
if n > 0 {
|
||||||
|
t.Log(string(data[:n]))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = reader.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DeflateReader struct {
|
type DeflateReader struct {
|
||||||
|
BaseReader
|
||||||
|
|
||||||
reader io.ReadCloser
|
reader io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeflateReader(reader io.Reader) (Reader, error) {
|
func NewDeflateReader(reader io.Reader) (Reader, error) {
|
||||||
|
return sharedDeflateReaderPool.Get(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeflateReader(reader io.Reader) (Reader, error) {
|
||||||
return &DeflateReader{reader: flate.NewReader(reader)}, nil
|
return &DeflateReader{reader: flate.NewReader(reader)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +25,15 @@ func (this *DeflateReader) Read(p []byte) (n int, err error) {
|
|||||||
return this.reader.Read(p)
|
return this.reader.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *DeflateReader) Close() error {
|
func (this *DeflateReader) Reset(reader io.Reader) error {
|
||||||
|
this.reader = flate.NewReader(reader)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DeflateReader) RawClose() error {
|
||||||
return this.reader.Close()
|
return this.reader.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *DeflateReader) Close() error {
|
||||||
|
return this.Finish(this)
|
||||||
|
}
|
||||||
|
|||||||
51
internal/compressions/reader_deflate_test.go
Normal file
51
internal/compressions/reader_deflate_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeflateReader(t *testing.T) {
|
||||||
|
for _, testString := range []string{"Hello", "World", "Ni", "Hao"} {
|
||||||
|
t.Log("===", testString, "===")
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewDeflateWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(testString))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := compressions.NewDeflateReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var data = make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(data)
|
||||||
|
if n > 0 {
|
||||||
|
t.Log(string(data[:n]))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = reader.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GzipReader struct {
|
type GzipReader struct {
|
||||||
|
BaseReader
|
||||||
|
|
||||||
reader *gzip.Reader
|
reader *gzip.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGzipReader(reader io.Reader) (Reader, error) {
|
func NewGzipReader(reader io.Reader) (Reader, error) {
|
||||||
|
return sharedGzipReaderPool.Get(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGzipReader(reader io.Reader) (Reader, error) {
|
||||||
r, err := gzip.NewReader(reader)
|
r, err := gzip.NewReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -25,6 +31,14 @@ func (this *GzipReader) Read(p []byte) (n int, err error) {
|
|||||||
return this.reader.Read(p)
|
return this.reader.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *GzipReader) Close() error {
|
func (this *GzipReader) Reset(reader io.Reader) error {
|
||||||
|
return this.reader.Reset(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *GzipReader) RawClose() error {
|
||||||
return this.reader.Close()
|
return this.reader.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *GzipReader) Close() error {
|
||||||
|
return this.Finish(this)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +1,106 @@
|
|||||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package compressions
|
package compressions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGzipReader(t *testing.T) {
|
func TestGzipReader(t *testing.T) {
|
||||||
fp, err := os.Open("/Users/WorkSpace/EdgeProject/EdgeCache/p43/36/7e/367e02720713fe05b66573a1d69b4f0a.cache")
|
for _, testString := range []string{"Hello", "World", "Ni", "Hao"} {
|
||||||
if err != nil {
|
t.Log("===", testString, "===")
|
||||||
// not fatal
|
var buf = &bytes.Buffer{}
|
||||||
t.Log(err)
|
writer, err := compressions.NewGzipWriter(buf, 5)
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = fp.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var buf = make([]byte, 32*1024)
|
|
||||||
cacheReader := caches.NewFileReader(fp)
|
|
||||||
err = cacheReader.Init()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var headerBuf = []byte{}
|
|
||||||
err = cacheReader.ReadHeader(buf, func(n int) (goNext bool, err error) {
|
|
||||||
headerBuf = append(headerBuf, buf[:n]...)
|
|
||||||
for {
|
|
||||||
nIndex := bytes.Index(headerBuf, []byte{'\n'})
|
|
||||||
if nIndex >= 0 {
|
|
||||||
row := headerBuf[:nIndex]
|
|
||||||
spaceIndex := bytes.Index(row, []byte{':'})
|
|
||||||
if spaceIndex <= 0 {
|
|
||||||
return false, errors.New("invalid header '" + string(row) + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
headerBuf = headerBuf[nIndex+1:]
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
reader, err := NewGzipReader(cacheReader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
n, err := reader.Read(buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(testString))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := compressions.NewGzipReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var data = make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(data)
|
||||||
|
if n > 0 {
|
||||||
|
t.Log(string(data[:n]))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Log(string(buf[:n]))
|
err = reader.Close()
|
||||||
_ = n
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGzipReader(b *testing.B) {
|
||||||
|
var randomData = func() []byte {
|
||||||
|
var b = strings.Builder{}
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
b.WriteString(types.String(rands.Int64() % 10))
|
||||||
|
}
|
||||||
|
return []byte(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewGzipWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = writer.Write(randomData())
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var newBytes = make([]byte, buf.Len())
|
||||||
|
copy(newBytes, buf.Bytes())
|
||||||
|
reader, err := compressions.NewGzipReader(bytes.NewReader(newBytes))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
var data = make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(data)
|
||||||
|
if n > 0 {
|
||||||
|
_ = data[:n]
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = reader.Close()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
internal/compressions/reader_pool.go
Normal file
56
internal/compressions/reader_pool.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReaderPool struct {
|
||||||
|
c chan Reader
|
||||||
|
newFunc func(reader io.Reader) (Reader, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReaderPool(maxSize int, newFunc func(reader io.Reader) (Reader, error)) *ReaderPool {
|
||||||
|
if maxSize <= 0 {
|
||||||
|
maxSize = 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ReaderPool{
|
||||||
|
c: make(chan Reader, maxSize),
|
||||||
|
newFunc: newFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ReaderPool) Get(parentReader io.Reader) (Reader, error) {
|
||||||
|
select {
|
||||||
|
case reader := <-this.c:
|
||||||
|
err := reader.Reset(parentReader)
|
||||||
|
if err != nil {
|
||||||
|
// create new
|
||||||
|
reader, err = this.newFunc(parentReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reader.SetPool(this)
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
reader.ResetFinish()
|
||||||
|
return reader, nil
|
||||||
|
default:
|
||||||
|
// create new
|
||||||
|
reader, err := this.newFunc(parentReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reader.SetPool(this)
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ReaderPool) Put(reader Reader) {
|
||||||
|
select {
|
||||||
|
case this.c <- reader:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
20
internal/compressions/reader_pool_brotli.go
Normal file
20
internal/compressions/reader_pool_brotli.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedBrotliReaderPool *ReaderPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var maxSize = utils.SystemMemoryGB() * 256
|
||||||
|
if maxSize == 0 {
|
||||||
|
maxSize = 256
|
||||||
|
}
|
||||||
|
sharedBrotliReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||||
|
return newBrotliReader(reader)
|
||||||
|
})
|
||||||
|
}
|
||||||
20
internal/compressions/reader_pool_deflate.go
Normal file
20
internal/compressions/reader_pool_deflate.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedDeflateReaderPool *ReaderPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var maxSize = utils.SystemMemoryGB() * 256
|
||||||
|
if maxSize == 0 {
|
||||||
|
maxSize = 256
|
||||||
|
}
|
||||||
|
sharedDeflateReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||||
|
return newDeflateReader(reader)
|
||||||
|
})
|
||||||
|
}
|
||||||
20
internal/compressions/reader_pool_gzip.go
Normal file
20
internal/compressions/reader_pool_gzip.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedGzipReaderPool *ReaderPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var maxSize = utils.SystemMemoryGB() * 256
|
||||||
|
if maxSize == 0 {
|
||||||
|
maxSize = 256
|
||||||
|
}
|
||||||
|
sharedGzipReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||||
|
return newGzipReader(reader)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -18,6 +18,11 @@ const (
|
|||||||
|
|
||||||
var ErrNotSupportedContentEncoding = errors.New("not supported content encoding")
|
var ErrNotSupportedContentEncoding = errors.New("not supported content encoding")
|
||||||
|
|
||||||
|
// AllEncodings 当前支持的所有编码
|
||||||
|
func AllEncodings() []ContentEncoding {
|
||||||
|
return []ContentEncoding{ContentEncodingBr, ContentEncodingGzip, ContentEncodingDeflate}
|
||||||
|
}
|
||||||
|
|
||||||
// NewReader 获取Reader
|
// NewReader 获取Reader
|
||||||
func NewReader(reader io.Reader, contentEncoding ContentEncoding) (Reader, error) {
|
func NewReader(reader io.Reader, contentEncoding ContentEncoding) (Reader, error) {
|
||||||
switch contentEncoding {
|
switch contentEncoding {
|
||||||
@@ -32,7 +37,6 @@ func NewReader(reader io.Reader, contentEncoding ContentEncoding) (Reader, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWriter 获取Writer
|
// NewWriter 获取Writer
|
||||||
// TODO 考虑重用Writer
|
|
||||||
func NewWriter(writer io.Writer, compressType serverconfigs.HTTPCompressionType, level int) (Writer, error) {
|
func NewWriter(writer io.Writer, compressType serverconfigs.HTTPCompressionType, level int) (Writer, error) {
|
||||||
switch compressType {
|
switch compressType {
|
||||||
case serverconfigs.HTTPCompressionTypeGzip:
|
case serverconfigs.HTTPCompressionTypeGzip:
|
||||||
|
|||||||
@@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
package compressions
|
package compressions
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
Write(p []byte) (int, error)
|
Write(p []byte) (int, error)
|
||||||
Flush() error
|
Flush() error
|
||||||
|
Reset(writer io.Writer)
|
||||||
|
RawClose() error
|
||||||
Close() error
|
Close() error
|
||||||
Level() int
|
Level() int
|
||||||
|
|
||||||
|
SetPool(pool *WriterPool)
|
||||||
|
ResetFinish()
|
||||||
}
|
}
|
||||||
|
|||||||
26
internal/compressions/writer_base.go
Normal file
26
internal/compressions/writer_base.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
type BaseWriter struct {
|
||||||
|
pool *WriterPool
|
||||||
|
|
||||||
|
isFinished bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseWriter) SetPool(pool *WriterPool) {
|
||||||
|
this.pool = pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseWriter) Finish(obj Writer) error {
|
||||||
|
err := obj.RawClose()
|
||||||
|
if err == nil && this.pool != nil && !this.isFinished {
|
||||||
|
this.pool.Put(obj)
|
||||||
|
}
|
||||||
|
this.isFinished = true
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseWriter) ResetFinish() {
|
||||||
|
this.isFinished = false
|
||||||
|
}
|
||||||
@@ -8,19 +8,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BrotliWriter struct {
|
type BrotliWriter struct {
|
||||||
|
BaseWriter
|
||||||
|
|
||||||
writer *brotli.Writer
|
writer *brotli.Writer
|
||||||
level int
|
level int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBrotliWriter(writer io.Writer, level int) (Writer, error) {
|
func NewBrotliWriter(writer io.Writer, level int) (Writer, error) {
|
||||||
|
return sharedBrotliWriterPool.Get(writer, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBrotliWriter(writer io.Writer, level int) (*BrotliWriter, error) {
|
||||||
if level <= 0 {
|
if level <= 0 {
|
||||||
level = brotli.BestSpeed
|
level = brotli.BestSpeed
|
||||||
} else if level > brotli.BestCompression {
|
} else if level > brotli.BestCompression {
|
||||||
level = brotli.BestCompression
|
level = brotli.BestCompression
|
||||||
}
|
}
|
||||||
return &BrotliWriter{
|
return &BrotliWriter{
|
||||||
writer: brotli.NewWriterLevel(writer, level),
|
writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{
|
||||||
level: level,
|
Quality: level,
|
||||||
|
LGWin: 13, // TODO 在全局设置里可以设置此值
|
||||||
|
}),
|
||||||
|
level: level,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,10 +41,18 @@ func (this *BrotliWriter) Flush() error {
|
|||||||
return this.writer.Flush()
|
return this.writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *BrotliWriter) Close() error {
|
func (this *BrotliWriter) Reset(newWriter io.Writer) {
|
||||||
|
this.writer.Reset(newWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BrotliWriter) RawClose() error {
|
||||||
return this.writer.Close()
|
return this.writer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *BrotliWriter) Close() error {
|
||||||
|
return this.Finish(this)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *BrotliWriter) Level() int {
|
func (this *BrotliWriter) Level() int {
|
||||||
return this.level
|
return this.level
|
||||||
}
|
}
|
||||||
|
|||||||
116
internal/compressions/writer_brotli_test.go
Normal file
116
internal/compressions/writer_brotli_test.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkBrotliWriter_Write(b *testing.B) {
|
||||||
|
var data = []byte(strings.Repeat("A", 1024))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}**/
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = writer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBrotliWriter_Write_Parallel(b *testing.B) {
|
||||||
|
var data = []byte(strings.Repeat("A", 1024))
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}**/
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = writer.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBrotliWriter_Write_Small(b *testing.B) {
|
||||||
|
var data = []byte(strings.Repeat("A", 16))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}**/
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = writer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBrotliWriter_Write_Large(b *testing.B) {
|
||||||
|
var data = []byte(strings.Repeat("A", 4096))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}**/
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = writer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,11 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DeflateWriter struct {
|
type DeflateWriter struct {
|
||||||
|
BaseWriter
|
||||||
|
|
||||||
writer *flate.Writer
|
writer *flate.Writer
|
||||||
level int
|
level int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
func NewDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
||||||
|
return sharedDeflateWriterPool.Get(writer, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
||||||
if level <= 0 {
|
if level <= 0 {
|
||||||
level = flate.BestSpeed
|
level = flate.BestSpeed
|
||||||
} else if level > flate.BestCompression {
|
} else if level > flate.BestCompression {
|
||||||
@@ -38,10 +44,18 @@ func (this *DeflateWriter) Flush() error {
|
|||||||
return this.writer.Flush()
|
return this.writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *DeflateWriter) Close() error {
|
func (this *DeflateWriter) Reset(writer io.Writer) {
|
||||||
|
this.writer.Reset(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DeflateWriter) RawClose() error {
|
||||||
return this.writer.Close()
|
return this.writer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *DeflateWriter) Close() error {
|
||||||
|
return this.Finish(this)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *DeflateWriter) Level() int {
|
func (this *DeflateWriter) Level() int {
|
||||||
return this.level
|
return this.level
|
||||||
}
|
}
|
||||||
|
|||||||
36
internal/compressions/writer_deflate_test.go
Normal file
36
internal/compressions/writer_deflate_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkDeflateWriter_Write(b *testing.B) {
|
||||||
|
var data = []byte(strings.Repeat("A", 1024))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewDeflateWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}**/
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = writer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// 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()
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// 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())
|
|
||||||
}
|
|
||||||
@@ -8,11 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GzipWriter struct {
|
type GzipWriter struct {
|
||||||
|
BaseWriter
|
||||||
|
|
||||||
writer *gzip.Writer
|
writer *gzip.Writer
|
||||||
level int
|
level int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGzipWriter(writer io.Writer, level int) (Writer, error) {
|
func NewGzipWriter(writer io.Writer, level int) (Writer, error) {
|
||||||
|
return sharedGzipWriterPool.Get(writer, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGzipWriter(writer io.Writer, level int) (Writer, error) {
|
||||||
if level <= 0 {
|
if level <= 0 {
|
||||||
level = gzip.BestSpeed
|
level = gzip.BestSpeed
|
||||||
} else if level > gzip.BestCompression {
|
} else if level > gzip.BestCompression {
|
||||||
@@ -38,10 +44,18 @@ func (this *GzipWriter) Flush() error {
|
|||||||
return this.writer.Flush()
|
return this.writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *GzipWriter) Close() error {
|
func (this *GzipWriter) Reset(writer io.Writer) {
|
||||||
|
this.writer.Reset(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *GzipWriter) RawClose() error {
|
||||||
return this.writer.Close()
|
return this.writer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *GzipWriter) Close() error {
|
||||||
|
return this.Finish(this)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *GzipWriter) Level() int {
|
func (this *GzipWriter) Level() int {
|
||||||
return this.level
|
return this.level
|
||||||
}
|
}
|
||||||
|
|||||||
36
internal/compressions/writer_gzip_test.go
Normal file
36
internal/compressions/writer_gzip_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkGzipWriter_Write(b *testing.B) {
|
||||||
|
var data = []byte(strings.Repeat("A", 1024))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
writer, err := compressions.NewGzipWriter(buf, 5)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}**/
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = writer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
61
internal/compressions/writer_pool.go
Normal file
61
internal/compressions/writer_pool.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WriterPool struct {
|
||||||
|
m map[int]chan Writer // level => chan Writer
|
||||||
|
newFunc func(writer io.Writer, level int) (Writer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriterPool(maxSize int, maxLevel int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool {
|
||||||
|
if maxSize <= 0 {
|
||||||
|
maxSize = 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = map[int]chan Writer{}
|
||||||
|
for i := 0; i <= maxLevel; i++ {
|
||||||
|
m[i] = make(chan Writer, maxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WriterPool{
|
||||||
|
m: m,
|
||||||
|
newFunc: newFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
|
||||||
|
c, ok := this.m[level]
|
||||||
|
if !ok {
|
||||||
|
c = this.m[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case writer := <-c:
|
||||||
|
writer.Reset(parentWriter)
|
||||||
|
writer.ResetFinish()
|
||||||
|
return writer, nil
|
||||||
|
default:
|
||||||
|
writer, err := this.newFunc(parentWriter, level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
writer.SetPool(this)
|
||||||
|
return writer, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *WriterPool) Put(writer Writer) {
|
||||||
|
var level = writer.Level()
|
||||||
|
c, ok := this.m[level]
|
||||||
|
if !ok {
|
||||||
|
c = this.m[0]
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case c <- writer:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
21
internal/compressions/writer_pool_brotli.go
Normal file
21
internal/compressions/writer_pool_brotli.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/andybalholm/brotli"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedBrotliWriterPool *WriterPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var maxSize = utils.SystemMemoryGB() * 256
|
||||||
|
if maxSize == 0 {
|
||||||
|
maxSize = 256
|
||||||
|
}
|
||||||
|
sharedBrotliWriterPool = NewWriterPool(maxSize, brotli.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||||
|
return newBrotliWriter(writer, level)
|
||||||
|
})
|
||||||
|
}
|
||||||
21
internal/compressions/writer_pool_deflate.go
Normal file
21
internal/compressions/writer_pool_deflate.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedDeflateWriterPool *WriterPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var maxSize = utils.SystemMemoryGB() * 256
|
||||||
|
if maxSize == 0 {
|
||||||
|
maxSize = 256
|
||||||
|
}
|
||||||
|
sharedDeflateWriterPool = NewWriterPool(maxSize, flate.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||||
|
return newDeflateWriter(writer, level)
|
||||||
|
})
|
||||||
|
}
|
||||||
21
internal/compressions/writer_pool_gzip.go
Normal file
21
internal/compressions/writer_pool_gzip.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package compressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedGzipWriterPool *WriterPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var maxSize = utils.SystemMemoryGB() * 256
|
||||||
|
if maxSize == 0 {
|
||||||
|
maxSize = 256
|
||||||
|
}
|
||||||
|
sharedGzipWriterPool = NewWriterPool(maxSize, gzip.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||||
|
return newGzipWriter(writer, level)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-yaml/yaml"
|
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 节点API配置
|
// APIConfig 节点API配置
|
||||||
type APIConfig struct {
|
type APIConfig struct {
|
||||||
RPC struct {
|
RPC struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints"`
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.4.2"
|
Version = "0.4.5"
|
||||||
|
|
||||||
ProductName = "Edge Node"
|
ProductName = "Edge Node"
|
||||||
ProcessName = "edge-node"
|
ProcessName = "edge-node"
|
||||||
|
|||||||
@@ -14,4 +14,7 @@ var (
|
|||||||
NodeIdString = ""
|
NodeIdString = ""
|
||||||
|
|
||||||
GlobalProductName = nodeconfigs.DefaultProductName
|
GlobalProductName = nodeconfigs.DefaultProductName
|
||||||
|
|
||||||
|
IsQuiting = false // 是否正在退出
|
||||||
|
EnableDBStat = false // 是否开启本地数据库统计
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package events
|
|||||||
type Event = string
|
type Event = string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EventStart Event = "start" // start loading
|
EventStart Event = "start" // start loading
|
||||||
EventLoaded Event = "loaded" // first load
|
EventLoaded Event = "loaded" // first load
|
||||||
EventQuit Event = "quit" // quit node gracefully
|
EventQuit Event = "quit" // quit node gracefully
|
||||||
EventReload Event = "reload" // reload config
|
EventReload Event = "reload" // reload config
|
||||||
|
EventTerminated Event = "terminated" // process terminated
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,6 +60,14 @@ func Remove(key interface{}) {
|
|||||||
|
|
||||||
// Notify 通知事件
|
// Notify 通知事件
|
||||||
func Notify(event Event) {
|
func Notify(event Event) {
|
||||||
|
// 特殊事件
|
||||||
|
switch event {
|
||||||
|
case EventQuit:
|
||||||
|
teaconst.IsQuiting = true
|
||||||
|
case EventTerminated:
|
||||||
|
teaconst.IsQuiting = true
|
||||||
|
}
|
||||||
|
|
||||||
locker.Lock()
|
locker.Lock()
|
||||||
m := eventsMap[event]
|
m := eventsMap[event]
|
||||||
locker.Unlock()
|
locker.Unlock()
|
||||||
|
|||||||
@@ -62,29 +62,39 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
{
|
|
||||||
cmd := exec.Command(path, "create", this.config.WhiteName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
// ipv4
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
for _, listName := range []string{this.config.WhiteName, this.config.BlackName} {
|
||||||
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var cmd = exec.Command(path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
||||||
|
var stderr = bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
if !bytes.Contains(output, []byte("already exists")) {
|
if !bytes.Contains(output, []byte("already exists")) {
|
||||||
return errors.New("create ipset '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
cmd := exec.Command(path, "create", this.config.BlackName, "hash:ip", "timeout", "0", "maxelem", "1000000")
|
// ipv6
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
for _, listName := range []string{this.config.WhiteNameIPv6, this.config.BlackNameIPv6} {
|
||||||
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var cmd = exec.Command(path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
|
||||||
|
var stderr = bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
if !bytes.Contains(output, []byte("already exists")) {
|
if !bytes.Contains(output, []byte("already exists")) {
|
||||||
return errors.New("create ipset '" + this.config.BlackName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
||||||
} else {
|
} else {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -99,8 +109,12 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// ipv4
|
||||||
cmd := exec.Command(path, "--permanent", "--new-ipset="+this.config.WhiteName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
for _, listName := range []string{this.config.WhiteName, this.config.BlackName} {
|
||||||
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
@@ -109,45 +123,57 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("firewall-cmd add ipset '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// ipv6
|
||||||
cmd := exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+this.config.WhiteName+"' accept")
|
for _, listName := range []string{this.config.WhiteNameIPv6, this.config.BlackNameIPv6} {
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
if len(listName) == 0 {
|
||||||
cmd.Stderr = stderr
|
continue
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
output := stderr.Bytes()
|
|
||||||
return errors.New("firewall-cmd add rich rule '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
|
||||||
}
|
}
|
||||||
}
|
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
|
|
||||||
{
|
|
||||||
cmd := exec.Command(path, "--permanent", "--new-ipset="+this.config.BlackName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("firewall-cmd add ipset '" + this.config.BlackName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// accept
|
||||||
cmd := exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+this.config.BlackName+"' reject")
|
for _, listName := range []string{this.config.WhiteName, this.config.WhiteNameIPv6} {
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
|
||||||
|
var stderr = bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
return errors.New("firewall-cmd add rich rule '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reject
|
||||||
|
for _, listName := range []string{this.config.BlackName, this.config.BlackNameIPv6} {
|
||||||
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
|
||||||
|
var stderr = bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
var output = stderr.Bytes()
|
||||||
|
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +184,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
|
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,38 +197,48 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// accept
|
||||||
|
for _, listName := range []string{this.config.WhiteName, this.config.WhiteNameIPv6} {
|
||||||
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 检查规则是否存在
|
// 检查规则是否存在
|
||||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", this.config.WhiteName, "src", "-j", "ACCEPT")
|
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
var exists = err == nil
|
var exists = err == nil
|
||||||
|
|
||||||
// 添加规则
|
// 添加规则
|
||||||
if !exists {
|
if !exists {
|
||||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.WhiteName, "src", "-j", "ACCEPT")
|
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
var stderr = bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// reject
|
||||||
|
for _, listName := range []string{this.config.BlackName, this.config.BlackNameIPv6} {
|
||||||
|
if len(listName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// 检查规则是否存在
|
// 检查规则是否存在
|
||||||
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", this.config.BlackName, "src", "-j", "REJECT")
|
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
var exists = err == nil
|
var exists = err == nil
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.BlackName, "src", "-j", "REJECT")
|
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
var stderr = bytes.NewBuffer([]byte{})
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output := stderr.Bytes()
|
var output = stderr.Bytes()
|
||||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +272,7 @@ func (this *IPSetAction) runAction(action string, listType IPListType, item *pb.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, cidr := range cidrList {
|
for _, cidr := range cidrList {
|
||||||
index := strings.Index(cidr, "/")
|
var index = strings.Index(cidr, "/")
|
||||||
if index <= 0 {
|
if index <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -256,26 +292,40 @@ func (this *IPSetAction) runAction(action string, listType IPListType, item *pb.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *IPSetAction) SetConfig(config *firewallconfigs.FirewallActionIPSetConfig) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
if item.Type == "all" {
|
if item.Type == "all" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
listName := ""
|
var listName = ""
|
||||||
|
var isIPv6 = strings.Contains(item.IpFrom, ":")
|
||||||
|
|
||||||
switch listType {
|
switch listType {
|
||||||
case IPListTypeWhite:
|
case IPListTypeWhite:
|
||||||
listName = this.config.WhiteName
|
if isIPv6 {
|
||||||
|
listName = this.config.WhiteNameIPv6
|
||||||
|
} else {
|
||||||
|
listName = this.config.WhiteName
|
||||||
|
}
|
||||||
case IPListTypeBlack:
|
case IPListTypeBlack:
|
||||||
listName = this.config.BlackName
|
if isIPv6 {
|
||||||
|
listName = this.config.BlackNameIPv6
|
||||||
|
} else {
|
||||||
|
listName = this.config.BlackName
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// 不支持的类型
|
// 不支持的类型
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(listName) == 0 {
|
if len(listName) == 0 {
|
||||||
return errors.New("empty list name for '" + listType + "'")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
path := this.config.Path
|
var path = this.config.Path
|
||||||
var err error
|
var err error
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
path, err = exec.LookPath("ipset")
|
path, err = exec.LookPath("ipset")
|
||||||
@@ -290,7 +340,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ipset add edge_ip_list 192.168.2.32 timeout 30
|
// ipset add edge_ip_list 192.168.2.32 timeout 30
|
||||||
args := []string{}
|
var args = []string{}
|
||||||
switch action {
|
switch action {
|
||||||
case "addItem":
|
case "addItem":
|
||||||
args = append(args, "add")
|
args = append(args, "add")
|
||||||
@@ -300,7 +350,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
|||||||
|
|
||||||
args = append(args, listName, item.IpFrom)
|
args = append(args, listName, item.IpFrom)
|
||||||
if action == "addItem" {
|
if action == "addItem" {
|
||||||
timestamp := time.Now().Unix()
|
var timestamp = time.Now().Unix()
|
||||||
if item.ExpiredAt > timestamp {
|
if item.ExpiredAt > timestamp {
|
||||||
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10))
|
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10))
|
||||||
}
|
}
|
||||||
@@ -312,7 +362,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.errorBuf.Reset()
|
this.errorBuf.Reset()
|
||||||
cmd := exec.Command(path, args...)
|
var cmd = exec.Command(path, args...)
|
||||||
cmd.Stderr = this.errorBuf
|
cmd.Stderr = this.errorBuf
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package iplibrary
|
package iplibrary_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIPSetAction_Init(t *testing.T) {
|
func TestIPSetAction_Init(t *testing.T) {
|
||||||
action := NewIPSetAction()
|
action := iplibrary.NewIPSetAction()
|
||||||
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||||
Params: maps.Map{
|
Params: maps.Map{
|
||||||
"path": "/usr/bin/iptables",
|
"path": "/usr/bin/iptables",
|
||||||
@@ -24,14 +25,16 @@ func TestIPSetAction_Init(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIPSetAction_AddItem(t *testing.T) {
|
func TestIPSetAction_AddItem(t *testing.T) {
|
||||||
action := NewIPSetAction()
|
var action = iplibrary.NewIPSetAction()
|
||||||
action.config = &firewallconfigs.FirewallActionIPSetConfig{
|
action.SetConfig(&firewallconfigs.FirewallActionIPSetConfig{
|
||||||
Path: "/usr/bin/iptables",
|
Path: "/usr/bin/iptables",
|
||||||
WhiteName: "white-list",
|
WhiteName: "white-list",
|
||||||
BlackName: "black-list",
|
BlackName: "black-list",
|
||||||
}
|
WhiteNameIPv6: "white-list-ipv6",
|
||||||
|
BlackNameIPv6: "black-list-ipv6",
|
||||||
|
})
|
||||||
{
|
{
|
||||||
err := action.AddItem(IPListTypeWhite, &pb.IPItem{
|
err := action.AddItem(iplibrary.IPListTypeWhite, &pb.IPItem{
|
||||||
Type: "ipv4",
|
Type: "ipv4",
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IpFrom: "192.168.1.100",
|
IpFrom: "192.168.1.100",
|
||||||
@@ -42,9 +45,20 @@ func TestIPSetAction_AddItem(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
err := action.AddItem(iplibrary.IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "1:2:3:4",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
err := action.AddItem(iplibrary.IPListTypeBlack, &pb.IPItem{
|
||||||
Type: "ipv4",
|
Type: "ipv4",
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IpFrom: "192.168.1.100",
|
IpFrom: "192.168.1.100",
|
||||||
@@ -55,10 +69,22 @@ func TestIPSetAction_AddItem(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
err := action.AddItem(iplibrary.IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "1:2:3:4",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIPSetAction_DeleteItem(t *testing.T) {
|
func TestIPSetAction_DeleteItem(t *testing.T) {
|
||||||
action := NewIPSetAction()
|
action := iplibrary.NewIPSetAction()
|
||||||
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||||
Params: maps.Map{
|
Params: maps.Map{
|
||||||
"path": "/usr/bin/firewalld",
|
"path": "/usr/bin/firewalld",
|
||||||
@@ -68,7 +94,7 @@ func TestIPSetAction_DeleteItem(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = action.DeleteItem(IPListTypeWhite, &pb.IPItem{
|
err = action.DeleteItem(iplibrary.IPListTypeWhite, &pb.IPItem{
|
||||||
Type: "ipv4",
|
Type: "ipv4",
|
||||||
Id: 1,
|
Id: 1,
|
||||||
IpFrom: "192.168.1.100",
|
IpFrom: "192.168.1.100",
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ func (this *IPList) sortItems() {
|
|||||||
|
|
||||||
// 不加锁的情况下查找Item
|
// 不加锁的情况下查找Item
|
||||||
func (this *IPList) lookupIP(ip uint64) *IPItem {
|
func (this *IPList) lookupIP(ip uint64) *IPItem {
|
||||||
|
if len(this.sortedItems) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var count = len(this.sortedItems)
|
var count = len(this.sortedItems)
|
||||||
var resultIndex = -1
|
var resultIndex = -1
|
||||||
sort.Search(count, func(i int) bool {
|
sort.Search(count, func(i int) bool {
|
||||||
|
|||||||
@@ -5,20 +5,27 @@ package iplibrary
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IPListDB struct {
|
type IPListDB struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
|
|
||||||
itemTableName string
|
itemTableName string
|
||||||
deleteItemStmt *sql.Stmt
|
deleteExpiredItemsStmt *sql.Stmt
|
||||||
insertItemStmt *sql.Stmt
|
deleteItemStmt *sql.Stmt
|
||||||
selectItemsStmt *sql.Stmt
|
insertItemStmt *sql.Stmt
|
||||||
|
selectItemsStmt *sql.Stmt
|
||||||
|
selectMaxVersionStmt *sql.Stmt
|
||||||
|
|
||||||
|
cleanTicker *time.Ticker
|
||||||
|
|
||||||
dir string
|
dir string
|
||||||
}
|
}
|
||||||
@@ -27,6 +34,7 @@ func NewIPListDB() (*IPListDB, error) {
|
|||||||
var db = &IPListDB{
|
var db = &IPListDB{
|
||||||
itemTableName: "ipItems",
|
itemTableName: "ipItems",
|
||||||
dir: filepath.Clean(Tea.Root + "/data"),
|
dir: filepath.Clean(Tea.Root + "/data"),
|
||||||
|
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||||
}
|
}
|
||||||
err := db.init()
|
err := db.init()
|
||||||
return db, err
|
return db, err
|
||||||
@@ -43,7 +51,7 @@ func (this *IPListDB) init() error {
|
|||||||
remotelogs.Println("CACHE", "create cache dir '"+this.dir+"'")
|
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")
|
db, err := sql.Open("sqlite3", "file:"+this.dir+"/ip_list.db?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -83,6 +91,11 @@ ON "` + this.itemTableName + `" (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 初始化SQL语句
|
// 初始化SQL语句
|
||||||
|
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -93,16 +106,37 @@ ON "` + this.itemTableName + `" (
|
|||||||
return err
|
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 ?, ?`)
|
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.selectMaxVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
||||||
|
|
||||||
this.db = db
|
this.db = db
|
||||||
|
|
||||||
|
goman.New(func() {
|
||||||
|
events.On(events.EventQuit, func() {
|
||||||
|
this.cleanTicker.Stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
for range this.cleanTicker.C {
|
||||||
|
err := this.DeleteExpiredItems()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteExpiredItems 删除过期的条目
|
||||||
|
func (this *IPListDB) DeleteExpiredItems() error {
|
||||||
|
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
||||||
_, err := this.deleteItemStmt.Exec(item.Id)
|
_, err := this.deleteItemStmt.Exec(item.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,11 +167,27 @@ func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadMaxVersion 读取当前最大版本号
|
||||||
|
func (this *IPListDB) ReadMaxVersion() int64 {
|
||||||
|
row := this.selectMaxVersionStmt.QueryRow()
|
||||||
|
if row == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var version int64
|
||||||
|
err = row.Scan(&version)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
func (this *IPListDB) Close() error {
|
func (this *IPListDB) Close() error {
|
||||||
if this.db != nil {
|
if this.db != nil {
|
||||||
|
_ = this.deleteExpiredItemsStmt.Close()
|
||||||
_ = this.deleteItemStmt.Close()
|
_ = this.deleteItemStmt.Close()
|
||||||
_ = this.insertItemStmt.Close()
|
_ = this.insertItemStmt.Close()
|
||||||
_ = this.selectItemsStmt.Close()
|
_ = this.selectItemsStmt.Close()
|
||||||
|
_ = this.selectMaxVersionStmt.Close()
|
||||||
|
|
||||||
return this.db.Close()
|
return this.db.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package iplibrary
|
package iplibrary_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||||
_ "github.com/iwind/TeaGo/bootstrap"
|
_ "github.com/iwind/TeaGo/bootstrap"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestIPListDB_AddItem(t *testing.T) {
|
func TestIPListDB_AddItem(t *testing.T) {
|
||||||
db, err := NewIPListDB()
|
db, err := iplibrary.NewIPListDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ func TestIPListDB_AddItem(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIPListDB_ReadItems(t *testing.T) {
|
func TestIPListDB_ReadItems(t *testing.T) {
|
||||||
db, err := NewIPListDB()
|
db, err := iplibrary.NewIPListDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -58,3 +59,11 @@ func TestIPListDB_ReadItems(t *testing.T) {
|
|||||||
}
|
}
|
||||||
logs.PrintAsJSON(items, t)
|
logs.PrintAsJSON(items, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIPListDB_ReadMaxVersion(t *testing.T) {
|
||||||
|
db, err := iplibrary.NewIPListDB()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(db.ReadMaxVersion())
|
||||||
|
}
|
||||||
|
|||||||
@@ -302,16 +302,20 @@ func TestTooManyLists(t *testing.T) {
|
|||||||
func BenchmarkIPList_Contains(b *testing.B) {
|
func BenchmarkIPList_Contains(b *testing.B) {
|
||||||
runtime.GOMAXPROCS(1)
|
runtime.GOMAXPROCS(1)
|
||||||
|
|
||||||
list := NewIPList()
|
var list = NewIPList()
|
||||||
for i := 1; i < 194; i++ {
|
for i := 1; i < 200_000; i++ {
|
||||||
list.Add(&IPItem{
|
list.AddDelay(&IPItem{
|
||||||
Id: int64(i),
|
Id: int64(i),
|
||||||
IPFrom: utils.IP2Long(strconv.Itoa(i%255) + "." + strconv.Itoa(i%255) + ".0.1"),
|
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||||
IPTo: utils.IP2Long(strconv.Itoa(i%255) + "." + strconv.Itoa(i%255) + ".0.1"),
|
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||||
ExpiredAt: time.Now().Unix() + 60,
|
ExpiredAt: time.Now().Unix() + 60,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
list.Sort()
|
||||||
|
|
||||||
b.Log(len(list.itemsMap), "ip")
|
b.Log(len(list.itemsMap), "ip")
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = list.Contains(utils.IP2Long("192.168.1.100"))
|
_ = list.Contains(utils.IP2Long("192.168.1.100"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,37 +8,37 @@ import (
|
|||||||
|
|
||||||
// AllowIP 检查IP是否被允许访问
|
// AllowIP 检查IP是否被允许访问
|
||||||
// 如果一个IP不在任何名单中,则允许访问
|
// 如果一个IP不在任何名单中,则允许访问
|
||||||
func AllowIP(ip string, serverId int64) bool {
|
func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool) {
|
||||||
var ipLong = utils.IP2Long(ip)
|
var ipLong = utils.IP2Long(ip)
|
||||||
if ipLong == 0 {
|
if ipLong == 0 {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check white lists
|
// check white lists
|
||||||
if GlobalWhiteIPList.Contains(ipLong) {
|
if GlobalWhiteIPList.Contains(ipLong) {
|
||||||
return true
|
return true, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverId > 0 {
|
if serverId > 0 {
|
||||||
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
||||||
if list != nil && list.Contains(ipLong) {
|
if list != nil && list.Contains(ipLong) {
|
||||||
return true
|
return true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check black lists
|
// check black lists
|
||||||
if GlobalBlackIPList.Contains(ipLong) {
|
if GlobalBlackIPList.Contains(ipLong) {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverId > 0 {
|
if serverId > 0 {
|
||||||
var list = SharedServerListManager.FindBlackList(serverId, false)
|
var list = SharedServerListManager.FindBlackList(serverId, false)
|
||||||
if list != nil && list.Contains(ipLong) {
|
if list != nil && list.Contains(ipLong) {
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInWhiteList 检查IP是否在白名单中
|
// IsInWhiteList 检查IP是否在白名单中
|
||||||
@@ -58,7 +58,7 @@ func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, ip := range ipStrings {
|
for _, ip := range ipStrings {
|
||||||
isAllowed := AllowIP(ip, serverId)
|
isAllowed, _ := AllowIP(ip, serverId)
|
||||||
if !isAllowed {
|
if !isAllowed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (this *IPListManager) Start() {
|
|||||||
if Tea.IsTesting() {
|
if Tea.IsTesting() {
|
||||||
this.ticker = time.NewTicker(10 * time.Second)
|
this.ticker = time.NewTicker(10 * time.Second)
|
||||||
}
|
}
|
||||||
countErrors := 0
|
var countErrors = 0
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-this.ticker.C:
|
case <-this.ticker.C:
|
||||||
@@ -100,6 +100,13 @@ func (this *IPListManager) init() {
|
|||||||
} else {
|
} else {
|
||||||
this.db = db
|
this.db = db
|
||||||
|
|
||||||
|
// 删除本地数据库中过期的条目
|
||||||
|
_ = db.DeleteExpiredItems()
|
||||||
|
|
||||||
|
// 本地数据库中最大版本号
|
||||||
|
this.version = db.ReadMaxVersion()
|
||||||
|
|
||||||
|
// 从本地数据库中加载
|
||||||
var offset int64 = 0
|
var offset int64 = 0
|
||||||
var size int64 = 1000
|
var size int64 = 1000
|
||||||
for {
|
for {
|
||||||
@@ -171,7 +178,7 @@ func (this *IPListManager) FindList(listId int64) *IPList {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool) {
|
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
var changedLists = map[*IPList]zero.Zero{}
|
var changedLists = map[*IPList]zero.Zero{}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
@@ -205,10 +212,10 @@ func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool)
|
|||||||
list.Delete(item.Id)
|
list.Delete(item.Id)
|
||||||
|
|
||||||
// 从WAF名单中删除
|
// 从WAF名单中删除
|
||||||
waf.SharedIPBlackList.RemoveIP(item.IpFrom, item.ServerId, shouldExecute)
|
waf.SharedIPBlackList.RemoveIP(item.IpFrom, item.ServerId, fromRemote)
|
||||||
|
|
||||||
// 操作事件
|
// 操作事件
|
||||||
if shouldExecute {
|
if fromRemote {
|
||||||
SharedActionManager.DeleteItem(item.ListType, item)
|
SharedActionManager.DeleteItem(item.ListType, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +232,7 @@ func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool)
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 事件操作
|
// 事件操作
|
||||||
if shouldExecute {
|
if fromRemote {
|
||||||
SharedActionManager.DeleteItem(item.ListType, item)
|
SharedActionManager.DeleteItem(item.ListType, item)
|
||||||
SharedActionManager.AddItem(item.ListType, item)
|
SharedActionManager.AddItem(item.ListType, item)
|
||||||
}
|
}
|
||||||
@@ -236,5 +243,11 @@ func (this *IPListManager) processItems(items []*pb.IPItem, shouldExecute bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
this.version = items[len(items)-1].Version
|
|
||||||
|
if fromRemote {
|
||||||
|
var latestVersion = items[len(items)-1].Version
|
||||||
|
if latestVersion > this.version {
|
||||||
|
this.version = latestVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -38,7 +39,7 @@ type Task struct {
|
|||||||
item *serverconfigs.MetricItemConfig
|
item *serverconfigs.MetricItemConfig
|
||||||
isLoaded bool
|
isLoaded bool
|
||||||
|
|
||||||
db *sql.DB
|
db *dbs.DB
|
||||||
statTableName string
|
statTableName string
|
||||||
isStopped bool
|
isStopped bool
|
||||||
|
|
||||||
@@ -87,12 +88,16 @@ func (this *Task) Init() error {
|
|||||||
remotelogs.Println("METRIC", "create data dir '"+dir+"'")
|
remotelogs.Println("METRIC", "create data dir '"+dir+"'")
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "file:"+dir+"/metric."+strconv.FormatInt(this.item.Id, 10)+".db?cache=shared&mode=rwc&_journal_mode=WAL")
|
db, err := sql.Open("sqlite3", "file:"+dir+"/metric."+strconv.FormatInt(this.item.Id, 10)+".db?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
this.db = db
|
this.db = dbs.NewDB(db)
|
||||||
|
|
||||||
|
if teaconst.EnableDBStat {
|
||||||
|
this.db.EnableStat(true)
|
||||||
|
}
|
||||||
|
|
||||||
//创建统计表
|
//创建统计表
|
||||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
|
||||||
@@ -438,10 +443,11 @@ func (this *Task) Upload(pauseDuration time.Duration) error {
|
|||||||
|
|
||||||
if len(idStrings) > 0 {
|
if len(idStrings) > 0 {
|
||||||
// 设置为已上传
|
// 设置为已上传
|
||||||
_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
|
// TODO 先不判断是否已经上传,需要改造API进行配合
|
||||||
|
/**_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}**/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/errors"
|
"github.com/TeaOSLab/EdgeNode/internal/errors"
|
||||||
@@ -182,7 +183,7 @@ func (this *APIStream) handleWriteCache(message *pb.NodeStreamMessage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expiredAt := time.Now().Unix() + msg.LifeSeconds
|
expiredAt := time.Now().Unix() + msg.LifeSeconds
|
||||||
writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, int64(len(msg.Value)), false)
|
writer, err := storage.OpenWriter(msg.Key, expiredAt, 200, int64(len(msg.Value)), -1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
|
this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -239,7 +240,7 @@ func (this *APIStream) handleReadCache(message *pb.NodeStreamMessage) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := storage.OpenReader(msg.Key, false)
|
reader, err := storage.OpenReader(msg.Key, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == caches.ErrNotFound {
|
if err == caches.ErrNotFound {
|
||||||
this.replyFail(message.RequestId, "key not found")
|
this.replyFail(message.RequestId, "key not found")
|
||||||
@@ -350,7 +351,16 @@ func (this *APIStream) handlePurgeCache(message *pb.NodeStreamMessage) error {
|
|||||||
if msg.Type == "file" {
|
if msg.Type == "file" {
|
||||||
var keys = msg.Keys
|
var keys = msg.Keys
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
keys = append(keys, key+webpSuffix)
|
keys = append(keys,
|
||||||
|
key+caches.SuffixMethod+"HEAD",
|
||||||
|
key+caches.SuffixWebP,
|
||||||
|
key+caches.SuffixPartial,
|
||||||
|
)
|
||||||
|
// TODO 根据实际缓存的内容进行组合
|
||||||
|
for _, encoding := range compressions.AllEncodings() {
|
||||||
|
keys = append(keys, key+caches.SuffixCompression+encoding)
|
||||||
|
keys = append(keys, key+caches.SuffixWebP+caches.SuffixCompression+encoding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
msg.Keys = keys
|
msg.Keys = keys
|
||||||
}
|
}
|
||||||
@@ -462,7 +472,7 @@ func (this *APIStream) handlePreheatCache(message *pb.NodeStreamMessage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expiredAt := time.Now().Unix() + 8600
|
expiredAt := time.Now().Unix() + 8600
|
||||||
writer, err := storage.OpenWriter(key, expiredAt, 200, resp.ContentLength, false) // TODO 可以设置缓存过期时间
|
writer, err := storage.OpenWriter(key, expiredAt, 200, resp.ContentLength, -1, false) // TODO 可以设置缓存过期时间
|
||||||
if err != nil {
|
if err != nil {
|
||||||
locker.Lock()
|
locker.Lock()
|
||||||
errorMessages = append(errorMessages, "open cache writer failed: "+key+": "+err.Error())
|
errorMessages = append(errorMessages, "open cache writer failed: "+key+": "+err.Error())
|
||||||
|
|||||||
@@ -49,8 +49,11 @@ func (this *ClientListener) Accept() (net.Conn, error) {
|
|||||||
// 是否在WAF名单中
|
// 是否在WAF名单中
|
||||||
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !iplibrary.AllowIP(ip, 0) || (!waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) &&
|
canGoNext, _ := iplibrary.AllowIP(ip, 0)
|
||||||
waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)) {
|
|
||||||
|
if !canGoNext ||
|
||||||
|
(!waf.SharedIPWhiteList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip) &&
|
||||||
|
waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, ip)) {
|
||||||
tcpConn, ok := conn.(*net.TCPConn)
|
tcpConn, ok := conn.(*net.TCPConn)
|
||||||
if ok {
|
if ok {
|
||||||
_ = tcpConn.SetLinger(0)
|
_ = tcpConn.SetLinger(0)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ Loop:
|
|||||||
// 是否包含了invalid UTF-8
|
// 是否包含了invalid UTF-8
|
||||||
if strings.Contains(err.Error(), "string field contains invalid UTF-8") {
|
if strings.Contains(err.Error(), "string field contains invalid UTF-8") {
|
||||||
for _, accessLog := range accessLogs {
|
for _, accessLog := range accessLogs {
|
||||||
this.toValidUTF8(accessLog)
|
this.ToValidUTF8(accessLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新提交
|
// 重新提交
|
||||||
@@ -108,7 +108,7 @@ Loop:
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *HTTPAccessLogQueue) toValidUTF8(accessLog *pb.HTTPAccessLog) {
|
func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) {
|
||||||
accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser)
|
accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser)
|
||||||
accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI)
|
accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI)
|
||||||
accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath)
|
accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath)
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package nodes
|
package nodes_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/nodes"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||||
_ "github.com/iwind/TeaGo/bootstrap"
|
_ "github.com/iwind/TeaGo/bootstrap"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -47,7 +50,7 @@ func TestHTTPAccessLogQueue_Push(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
new(HTTPAccessLogQueue).toValidUTF8(accessLog)
|
new(nodes.HTTPAccessLogQueue).ToValidUTF8(accessLog)
|
||||||
|
|
||||||
// logs.PrintAsJSON(accessLog)
|
// logs.PrintAsJSON(accessLog)
|
||||||
|
|
||||||
@@ -105,6 +108,33 @@ func TestHTTPAccessLogQueue_Push2(t *testing.T) {
|
|||||||
t.Log("ok")
|
t.Log("ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPAccessLogQueue_Memory(t *testing.T) {
|
||||||
|
testutils.StartMemoryStats(t)
|
||||||
|
|
||||||
|
debug.SetGCPercent(10)
|
||||||
|
|
||||||
|
var accessLogs = []*pb.HTTPAccessLog{}
|
||||||
|
for i := 0; i < 20_000; i++ {
|
||||||
|
accessLogs = append(accessLogs, &pb.HTTPAccessLog{
|
||||||
|
RequestPath: "https://goedge.cn/hello/world",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
|
// will not release automatically
|
||||||
|
func() {
|
||||||
|
var accessLogs1 = []*pb.HTTPAccessLog{}
|
||||||
|
for i := 0; i < 2_000_000; i++ {
|
||||||
|
accessLogs1 = append(accessLogs1, &pb.HTTPAccessLog{
|
||||||
|
RequestPath: "https://goedge.cn/hello/world",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkHTTPAccessLogQueue_ToValidUTF8(b *testing.B) {
|
func BenchmarkHTTPAccessLogQueue_ToValidUTF8(b *testing.B) {
|
||||||
runtime.GOMAXPROCS(1)
|
runtime.GOMAXPROCS(1)
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func NewHTTPClientPool() *HTTPClientPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Client 根据地址获取客户端
|
// Client 根据地址获取客户端
|
||||||
func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.OriginConfig, originAddr string, proxyProtocol *serverconfigs.ProxyProtocolConfig) (rawClient *http.Client, err error) {
|
func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.OriginConfig, originAddr string, proxyProtocol *serverconfigs.ProxyProtocolConfig, followRedirects bool) (rawClient *http.Client, err error) {
|
||||||
if origin.Addr == nil {
|
if origin.Addr == nil {
|
||||||
return nil, errors.New("origin addr should not be empty (originId:" + strconv.FormatInt(origin.Id, 10) + ")")
|
return nil, errors.New("origin addr should not be empty (originId:" + strconv.FormatInt(origin.Id, 10) + ")")
|
||||||
}
|
}
|
||||||
@@ -139,6 +139,19 @@ func (this *HTTPClientPool) Client(req *HTTPRequest, origin *serverconfigs.Origi
|
|||||||
Timeout: readTimeout,
|
Timeout: readTimeout,
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
if followRedirects {
|
||||||
|
var schemeIsSame = true
|
||||||
|
for _, r := range via {
|
||||||
|
if r.URL.Scheme != req.URL.Scheme {
|
||||||
|
schemeIsSame = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if schemeIsSame {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ func TestHTTPClientPool_Client(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log("client:", client)
|
t.Log("client:", client)
|
||||||
}
|
}
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ func TestHTTPClientPool_cleanClients(t *testing.T) {
|
|||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
t.Log("get", i)
|
t.Log("get", i)
|
||||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +73,6 @@ func BenchmarkHTTPClientPool_Client(b *testing.B) {
|
|||||||
|
|
||||||
pool := NewHTTPClientPool()
|
pool := NewHTTPClientPool()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil)
|
_, _ = pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -35,6 +38,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 X-Cache Header
|
||||||
var addStatusHeader = this.web.Cache.AddStatusHeader
|
var addStatusHeader = this.web.Cache.AddStatusHeader
|
||||||
if addStatusHeader {
|
if addStatusHeader {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -62,7 +66,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if this.cacheRef == nil {
|
if this.cacheRef == nil && !this.web.Cache.DisablePolicyRefs {
|
||||||
// 检查策略默认的缓存条件
|
// 检查策略默认的缓存条件
|
||||||
for _, cacheRef := range cachePolicy.CacheRefs {
|
for _, cacheRef := range cachePolicy.CacheRefs {
|
||||||
if !cacheRef.IsOn ||
|
if !cacheRef.IsOn ||
|
||||||
@@ -79,10 +83,10 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if this.cacheRef == nil {
|
if this.cacheRef == nil {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验请求
|
// 校验请求
|
||||||
@@ -106,12 +110,20 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
|
|
||||||
// TODO 支持Vary Header
|
// TODO 支持Vary Header
|
||||||
|
|
||||||
|
// 缓存标签
|
||||||
|
var tags = []string{}
|
||||||
|
|
||||||
// 检查是否有缓存
|
// 检查是否有缓存
|
||||||
key := this.Format(this.cacheRef.Key)
|
var key = this.Format(this.cacheRef.Key)
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
this.cacheRef = nil
|
this.cacheRef = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var method = this.Method()
|
||||||
|
if method != http.MethodGet {
|
||||||
|
key += caches.SuffixMethod + method
|
||||||
|
tags = append(tags, strings.ToLower(method))
|
||||||
|
}
|
||||||
|
|
||||||
this.cacheKey = key
|
this.cacheKey = key
|
||||||
this.varMapping["cache.key"] = key
|
this.varMapping["cache.key"] = key
|
||||||
@@ -122,16 +134,32 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
this.cacheRef = nil
|
this.cacheRef = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.writer.cacheStorage = storage
|
||||||
|
|
||||||
// 判断是否在Purge
|
// 判断是否在Purge
|
||||||
if this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey {
|
if this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey {
|
||||||
this.varMapping["cache.status"] = "PURGE"
|
this.varMapping["cache.status"] = "PURGE"
|
||||||
|
|
||||||
err := storage.Delete(key)
|
var subKeys = []string{
|
||||||
if err != nil {
|
key,
|
||||||
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
|
key + caches.SuffixMethod + "HEAD",
|
||||||
|
key + caches.SuffixWebP,
|
||||||
|
key + caches.SuffixPartial,
|
||||||
|
}
|
||||||
|
// TODO 根据实际缓存的内容进行组合
|
||||||
|
for _, encoding := range compressions.AllEncodings() {
|
||||||
|
subKeys = append(subKeys, key+caches.SuffixCompression+encoding)
|
||||||
|
subKeys = append(subKeys, key+caches.SuffixWebP+caches.SuffixCompression+encoding)
|
||||||
|
}
|
||||||
|
for _, subKey := range subKeys {
|
||||||
|
err := storage.Delete(subKey)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通过API节点清除别节点上的的Key
|
||||||
|
// TODO 改为队列,不需要每个请求都使用goroutine
|
||||||
goman.New(func() {
|
goman.New(func() {
|
||||||
rpcClient, err := rpc.SharedRPC()
|
rpcClient, err := rpc.SharedRPC()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -160,21 +188,72 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
var reader caches.Reader
|
var reader caches.Reader
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// 是否优先检查WebP
|
var rangeHeader = this.RawReq.Header.Get("Range")
|
||||||
var isWebP = false
|
var isPartialRequest = len(rangeHeader) > 0
|
||||||
if this.web.WebP != nil &&
|
|
||||||
|
// 检查是否支持WebP
|
||||||
|
var webPIsEnabled = false
|
||||||
|
var isHeadMethod = method == http.MethodHead
|
||||||
|
if !isPartialRequest &&
|
||||||
|
!isHeadMethod &&
|
||||||
|
this.web.WebP != nil &&
|
||||||
this.web.WebP.IsOn &&
|
this.web.WebP.IsOn &&
|
||||||
this.web.WebP.MatchRequest(filepath.Ext(this.Path()), this.Format) &&
|
this.web.WebP.MatchRequest(filepath.Ext(this.Path()), this.Format) &&
|
||||||
this.web.WebP.MatchAccept(this.requestHeader("Accept")) {
|
this.web.WebP.MatchAccept(this.RawReq.Header.Get("Accept")) {
|
||||||
reader, _ = storage.OpenReader(key+webpSuffix, useStale)
|
webPIsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查压缩缓存
|
||||||
|
if !isPartialRequest && !isHeadMethod && reader == nil {
|
||||||
|
if this.web.Compression != nil && this.web.Compression.IsOn {
|
||||||
|
_, encoding, ok := this.web.Compression.MatchAcceptEncoding(this.RawReq.Header.Get("Accept-Encoding"))
|
||||||
|
if ok {
|
||||||
|
// 检查支持WebP的压缩缓存
|
||||||
|
if webPIsEnabled {
|
||||||
|
reader, _ = storage.OpenReader(key+caches.SuffixWebP+caches.SuffixCompression+encoding, useStale, false)
|
||||||
|
if reader != nil {
|
||||||
|
tags = append(tags, "webp", encoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查普通压缩缓存
|
||||||
|
if reader == nil {
|
||||||
|
reader, _ = storage.OpenReader(key+caches.SuffixCompression+encoding, useStale, false)
|
||||||
|
if reader != nil {
|
||||||
|
tags = append(tags, encoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查WebP
|
||||||
|
if !isPartialRequest &&
|
||||||
|
!isHeadMethod &&
|
||||||
|
reader == nil &&
|
||||||
|
webPIsEnabled {
|
||||||
|
reader, _ = storage.OpenReader(key+caches.SuffixWebP, useStale, false)
|
||||||
if reader != nil {
|
if reader != nil {
|
||||||
isWebP = true
|
this.writer.cacheReaderSuffix = caches.SuffixWebP
|
||||||
|
tags = append(tags, "webp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查正常的文件
|
// 检查正常的文件
|
||||||
|
var isPartialCache = false
|
||||||
|
var partialRanges []rangeutils.Range
|
||||||
if reader == nil {
|
if reader == nil {
|
||||||
reader, err = storage.OpenReader(key, useStale)
|
reader, err = storage.OpenReader(key, useStale, false)
|
||||||
|
if err != nil && this.cacheRef.AllowPartialContent {
|
||||||
|
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader)
|
||||||
|
if pReader != nil {
|
||||||
|
isPartialCache = true
|
||||||
|
reader = pReader
|
||||||
|
partialRanges = ranges
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == caches.ErrNotFound {
|
if err == caches.ErrNotFound {
|
||||||
// cache相关变量
|
// cache相关变量
|
||||||
@@ -208,7 +287,16 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 准备Buffer
|
// 准备Buffer
|
||||||
var pool = this.bytePool(reader.BodySize())
|
var fileSize = reader.BodySize()
|
||||||
|
var totalSizeString = types.String(fileSize)
|
||||||
|
if isPartialCache {
|
||||||
|
fileSize = reader.(*caches.PartialFileReader).MaxLength()
|
||||||
|
if totalSizeString == "0" {
|
||||||
|
totalSizeString = "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pool = this.bytePool(fileSize)
|
||||||
var buf = pool.Get()
|
var buf = pool.Get()
|
||||||
defer func() {
|
defer func() {
|
||||||
pool.Put(buf)
|
pool.Put(buf)
|
||||||
@@ -265,13 +353,15 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
var eTag = ""
|
var eTag = ""
|
||||||
var lastModifiedAt = reader.LastModified()
|
var lastModifiedAt = reader.LastModified()
|
||||||
if lastModifiedAt > 0 {
|
if lastModifiedAt > 0 {
|
||||||
if isWebP {
|
if len(tags) > 0 {
|
||||||
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "_webp" + "\""
|
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "_" + strings.Join(tags, "_") + "\""
|
||||||
} else {
|
} else {
|
||||||
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\""
|
eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\""
|
||||||
}
|
}
|
||||||
respHeader.Del("Etag")
|
respHeader.Del("Etag")
|
||||||
respHeader["ETag"] = []string{eTag}
|
if !isPartialCache {
|
||||||
|
respHeader["ETag"] = []string{eTag}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支持 Last-Modified
|
// 支持 Last-Modified
|
||||||
@@ -279,11 +369,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
var modifiedTime = ""
|
var modifiedTime = ""
|
||||||
if lastModifiedAt > 0 {
|
if lastModifiedAt > 0 {
|
||||||
modifiedTime = time.Unix(utils.GMTUnixTime(lastModifiedAt), 0).Format("Mon, 02 Jan 2006 15:04:05") + " GMT"
|
modifiedTime = time.Unix(utils.GMTUnixTime(lastModifiedAt), 0).Format("Mon, 02 Jan 2006 15:04:05") + " GMT"
|
||||||
respHeader.Set("Last-Modified", modifiedTime)
|
if !isPartialCache {
|
||||||
|
respHeader.Set("Last-Modified", modifiedTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支持 If-None-Match
|
// 支持 If-None-Match
|
||||||
if len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
|
if !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusNotModified)
|
this.processResponseHeaders(http.StatusNotModified)
|
||||||
this.writer.WriteHeader(http.StatusNotModified)
|
this.writer.WriteHeader(http.StatusNotModified)
|
||||||
@@ -294,7 +386,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 支持 If-Modified-Since
|
// 支持 If-Modified-Since
|
||||||
if len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
|
if !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
|
||||||
// 自定义Header
|
// 自定义Header
|
||||||
this.processResponseHeaders(http.StatusNotModified)
|
this.processResponseHeaders(http.StatusNotModified)
|
||||||
this.writer.WriteHeader(http.StatusNotModified)
|
this.writer.WriteHeader(http.StatusNotModified)
|
||||||
@@ -312,69 +404,56 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
this.writer.WriteHeader(reader.Status())
|
this.writer.WriteHeader(reader.Status())
|
||||||
} else {
|
} else {
|
||||||
ifRangeHeaders, ok := this.RawReq.Header["If-Range"]
|
ifRangeHeaders, ok := this.RawReq.Header["If-Range"]
|
||||||
supportRange := true
|
var supportRange = true
|
||||||
if ok {
|
if ok {
|
||||||
supportRange = false
|
supportRange = false
|
||||||
for _, v := range ifRangeHeaders {
|
for _, v := range ifRangeHeaders {
|
||||||
if v == this.writer.Header().Get("ETag") || v == this.writer.Header().Get("Last-Modified") {
|
if v == this.writer.Header().Get("ETag") || v == this.writer.Header().Get("Last-Modified") {
|
||||||
supportRange = true
|
supportRange = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支持Range
|
// 支持Range
|
||||||
rangeSet := [][]int64{}
|
var ranges = partialRanges
|
||||||
if supportRange {
|
if supportRange {
|
||||||
fileSize := reader.BodySize()
|
if len(rangeHeader) > 0 {
|
||||||
contentRange := this.RawReq.Header.Get("Range")
|
|
||||||
if len(contentRange) > 0 {
|
|
||||||
if fileSize == 0 {
|
if fileSize == 0 {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
set, ok := httpRequestParseContentRange(contentRange)
|
if len(ranges) == 0 {
|
||||||
if !ok {
|
ranges, ok = httpRequestParseRangeHeader(rangeHeader)
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
if !ok {
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(set) > 0 {
|
if len(ranges) > 0 {
|
||||||
rangeSet = set
|
for k, r := range ranges {
|
||||||
for _, arr := range rangeSet {
|
r2, ok := r.Convert(fileSize)
|
||||||
if arr[0] == -1 {
|
if !ok {
|
||||||
arr[0] = fileSize + arr[1]
|
|
||||||
arr[1] = fileSize - 1
|
|
||||||
|
|
||||||
if arr[0] < 0 {
|
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if arr[1] < 0 {
|
|
||||||
arr[1] = fileSize - 1
|
|
||||||
}
|
|
||||||
if arr[1] >= fileSize {
|
|
||||||
arr[1] = fileSize - 1
|
|
||||||
}
|
|
||||||
if arr[0] > arr[1] {
|
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ranges[k] = r2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rangeSet) == 1 {
|
if len(ranges) == 1 {
|
||||||
respHeader.Set("Content-Range", "bytes "+strconv.FormatInt(rangeSet[0][0], 10)+"-"+strconv.FormatInt(rangeSet[0][1], 10)+"/"+strconv.FormatInt(reader.BodySize(), 10))
|
respHeader.Set("Content-Range", ranges[0].ComposeContentRangeHeader(totalSizeString))
|
||||||
respHeader.Set("Content-Length", strconv.FormatInt(rangeSet[0][1]-rangeSet[0][0]+1, 10))
|
respHeader.Set("Content-Length", strconv.FormatInt(ranges[0].Length(), 10))
|
||||||
this.writer.WriteHeader(http.StatusPartialContent)
|
this.writer.WriteHeader(http.StatusPartialContent)
|
||||||
|
|
||||||
err = reader.ReadBodyRange(buf, rangeSet[0][0], rangeSet[0][1], func(n int) (goNext bool, err error) {
|
err = reader.ReadBodyRange(buf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, err error) {
|
||||||
_, err = this.writer.Write(buf[:n])
|
_, err = this.writer.Write(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errWritingToClient
|
return false, errWritingToClient
|
||||||
@@ -394,15 +473,15 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if len(rangeSet) > 1 {
|
} else if len(ranges) > 1 {
|
||||||
boundary := httpRequestGenBoundary()
|
var boundary = httpRequestGenBoundary()
|
||||||
respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
|
respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
|
||||||
respHeader.Del("Content-Length")
|
respHeader.Del("Content-Length")
|
||||||
contentType := respHeader.Get("Content-Type")
|
contentType := respHeader.Get("Content-Type")
|
||||||
|
|
||||||
this.writer.WriteHeader(http.StatusPartialContent)
|
this.writer.WriteHeader(http.StatusPartialContent)
|
||||||
|
|
||||||
for index, set := range rangeSet {
|
for index, r := range ranges {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
_, err = this.writer.WriteString("--" + boundary + "\r\n")
|
_, err = this.writer.WriteString("--" + boundary + "\r\n")
|
||||||
} else {
|
} else {
|
||||||
@@ -413,7 +492,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = this.writer.WriteString("Content-Range: " + "bytes " + strconv.FormatInt(set[0], 10) + "-" + strconv.FormatInt(set[1], 10) + "/" + strconv.FormatInt(reader.BodySize(), 10) + "\r\n")
|
_, err = this.writer.WriteString("Content-Range: " + r.ComposeContentRangeHeader(totalSizeString) + "\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 不提示写入客户端错误
|
// 不提示写入客户端错误
|
||||||
return true
|
return true
|
||||||
@@ -427,7 +506,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := reader.ReadBodyRange(buf, set[0], set[1], func(n int) (goNext bool, err error) {
|
err := reader.ReadBodyRange(buf, r.Start(), r.End(), func(n int) (goNext bool, err error) {
|
||||||
_, err = this.writer.Write(buf[:n])
|
_, err = this.writer.Write(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errWritingToClient
|
return false, errWritingToClient
|
||||||
@@ -451,7 +530,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
}
|
}
|
||||||
} else { // 没有Range
|
} else { // 没有Range
|
||||||
var resp = &http.Response{Body: reader}
|
var resp = &http.Response{Body: reader}
|
||||||
this.writer.Prepare(resp, reader.BodySize(), reader.Status(), false)
|
this.writer.Prepare(resp, fileSize, reader.Status(), false)
|
||||||
this.writer.WriteHeader(reader.Status())
|
this.writer.WriteHeader(reader.Status())
|
||||||
|
|
||||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||||
@@ -492,3 +571,49 @@ func (this *HTTPRequest) addExpiresHeader(expiresAt int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 尝试读取区间缓存
|
||||||
|
func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key string, useStale bool, rangeHeader string) (caches.Reader, []rangeutils.Range) {
|
||||||
|
// 尝试读取Partial cache
|
||||||
|
if len(rangeHeader) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges, ok := httpRequestParseRangeHeader(rangeHeader)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pReader, pErr := storage.OpenReader(key+caches.SuffixPartial, useStale, true)
|
||||||
|
if pErr != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
partialReader, ok := pReader.(*caches.PartialFileReader)
|
||||||
|
if !ok {
|
||||||
|
_ = pReader.Close()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var isOk = false
|
||||||
|
defer func() {
|
||||||
|
if !isOk {
|
||||||
|
_ = pReader.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 检查范围
|
||||||
|
for index, r := range ranges {
|
||||||
|
r1, ok := r.Convert(partialReader.MaxLength())
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
r2, ok := partialReader.ContainsRange(r1)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ranges[index] = r2
|
||||||
|
}
|
||||||
|
|
||||||
|
isOk = true
|
||||||
|
return pReader, ranges
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,22 +21,22 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 对URL的处理
|
// 对URL的处理
|
||||||
stripPrefix := this.reverseProxy.StripPrefix
|
var stripPrefix = this.reverseProxy.StripPrefix
|
||||||
requestURI := this.reverseProxy.RequestURI
|
var requestURI = this.reverseProxy.RequestURI
|
||||||
requestURIHasVariables := this.reverseProxy.RequestURIHasVariables()
|
var requestURIHasVariables = this.reverseProxy.RequestURIHasVariables()
|
||||||
|
|
||||||
var requestHost = ""
|
var requestHost = ""
|
||||||
if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeCustomized {
|
if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeCustomized {
|
||||||
requestHost = this.reverseProxy.RequestHost
|
requestHost = this.reverseProxy.RequestHost
|
||||||
}
|
}
|
||||||
requestHostHasVariables := this.reverseProxy.RequestHostHasVariables()
|
var requestHostHasVariables = this.reverseProxy.RequestHostHasVariables()
|
||||||
|
|
||||||
// 源站
|
// 源站
|
||||||
requestCall := shared.NewRequestCall()
|
var requestCall = shared.NewRequestCall()
|
||||||
requestCall.Request = this.RawReq
|
requestCall.Request = this.RawReq
|
||||||
requestCall.Formatter = this.Format
|
requestCall.Formatter = this.Format
|
||||||
requestCall.Domain = this.ReqHost
|
requestCall.Domain = this.ReqHost
|
||||||
origin := this.reverseProxy.NextOrigin(requestCall)
|
var origin = this.reverseProxy.NextOrigin(requestCall)
|
||||||
requestCall.CallResponseCallbacks(this.writer)
|
requestCall.CallResponseCallbacks(this.writer)
|
||||||
if origin == nil {
|
if origin == nil {
|
||||||
err := errors.New(this.URL() + ": no available origin sites for reverse proxy")
|
err := errors.New(this.URL() + ": no available origin sites for reverse proxy")
|
||||||
@@ -89,9 +89,9 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理RequestURI中的问号
|
// 处理RequestURI中的问号
|
||||||
questionMark := strings.LastIndex(this.uri, "?")
|
var questionMark = strings.LastIndex(this.uri, "?")
|
||||||
if questionMark > 0 {
|
if questionMark > 0 {
|
||||||
path := this.uri[:questionMark]
|
var path = this.uri[:questionMark]
|
||||||
if strings.Contains(path, "?") {
|
if strings.Contains(path, "?") {
|
||||||
this.uri = path + "&" + this.uri[questionMark+1:]
|
this.uri = path + "&" + this.uri[questionMark+1:]
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取源站地址
|
// 获取源站地址
|
||||||
originAddr := origin.Addr.PickAddress()
|
var originAddr = origin.Addr.PickAddress()
|
||||||
if origin.Addr.HostHasVariables() {
|
if origin.Addr.HostHasVariables() {
|
||||||
originAddr = this.Format(originAddr)
|
originAddr = this.Format(originAddr)
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
if requestHostHasVariables {
|
if requestHostHasVariables {
|
||||||
this.RawReq.Host = this.Format(requestHost)
|
this.RawReq.Host = this.Format(requestHost)
|
||||||
} else {
|
} else {
|
||||||
this.RawReq.Host = this.reverseProxy.RequestHost
|
this.RawReq.Host = requestHost
|
||||||
}
|
}
|
||||||
this.RawReq.URL.Host = this.RawReq.Host
|
this.RawReq.URL.Host = this.RawReq.Host
|
||||||
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
|
} else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin {
|
||||||
@@ -132,7 +132,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重组请求URL
|
// 重组请求URL
|
||||||
questionMark := strings.Index(this.uri, "?")
|
var questionMark = strings.Index(this.uri, "?")
|
||||||
if questionMark > -1 {
|
if questionMark > -1 {
|
||||||
this.RawReq.URL.Path = this.uri[:questionMark]
|
this.RawReq.URL.Path = this.uri[:questionMark]
|
||||||
this.RawReq.URL.RawQuery = this.uri[questionMark+1:]
|
this.RawReq.URL.RawQuery = this.uri[questionMark+1:]
|
||||||
@@ -159,7 +159,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取请求客户端
|
// 获取请求客户端
|
||||||
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol)
|
client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||||
this.write50x(err, http.StatusBadGateway, true)
|
this.write50x(err, http.StatusBadGateway, true)
|
||||||
@@ -199,7 +199,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 是否为客户端方面的错误
|
// 是否为客户端方面的错误
|
||||||
isClientError := false
|
var isClientError = false
|
||||||
if ok {
|
if ok {
|
||||||
if httpErr.Err == context.Canceled {
|
if httpErr.Err == context.Canceled {
|
||||||
// 如果是服务器端主动关闭,则无需提示
|
// 如果是服务器端主动关闭,则无需提示
|
||||||
@@ -254,7 +254,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
|
if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
|
||||||
contentTypes, ok := resp.Header["Content-Type"]
|
contentTypes, ok := resp.Header["Content-Type"]
|
||||||
if ok && len(contentTypes) > 0 {
|
if ok && len(contentTypes) > 0 {
|
||||||
contentType := contentTypes[0]
|
var contentType = contentTypes[0]
|
||||||
if _, found := textMimeMap[contentType]; found {
|
if _, found := textMimeMap[contentType]; found {
|
||||||
resp.Header["Content-Type"][0] = contentType + "; charset=" + this.web.Charset.Charset
|
resp.Header["Content-Type"][0] = contentType + "; charset=" + this.web.Charset.Charset
|
||||||
}
|
}
|
||||||
@@ -284,8 +284,8 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输出到客户端
|
// 输出到客户端
|
||||||
pool := this.bytePool(resp.ContentLength)
|
var pool = this.bytePool(resp.ContentLength)
|
||||||
buf := pool.Get()
|
var buf = pool.Get()
|
||||||
if shouldAutoFlush {
|
if shouldAutoFlush {
|
||||||
for {
|
for {
|
||||||
n, readErr := resp.Body.Read(buf)
|
n, readErr := resp.Body.Read(buf)
|
||||||
@@ -306,7 +306,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
pool.Put(buf)
|
pool.Put(buf)
|
||||||
|
|
||||||
closeErr := resp.Body.Close()
|
var closeErr = resp.Body.Close()
|
||||||
if closeErr != nil {
|
if closeErr != nil {
|
||||||
if !this.canIgnore(closeErr) {
|
if !this.canIgnore(closeErr) {
|
||||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", closeErr.Error())
|
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", closeErr.Error())
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package nodes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"mime"
|
"mime"
|
||||||
@@ -186,7 +188,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// length
|
// length
|
||||||
fileSize := stat.Size()
|
var fileSize = stat.Size()
|
||||||
|
|
||||||
// 支持 Last-Modified
|
// 支持 Last-Modified
|
||||||
modifiedTime := stat.ModTime().Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
modifiedTime := stat.ModTime().Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||||
@@ -231,6 +233,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
for _, v := range ifRangeHeaders {
|
for _, v := range ifRangeHeaders {
|
||||||
if v == eTag || v == modifiedTime {
|
if v == eTag || v == modifiedTime {
|
||||||
supportRange = true
|
supportRange = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !supportRange {
|
if !supportRange {
|
||||||
@@ -239,7 +242,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 支持Range
|
// 支持Range
|
||||||
rangeSet := [][]int64{}
|
var ranges = []rangeutils.Range{}
|
||||||
if supportRange {
|
if supportRange {
|
||||||
contentRange := this.RawReq.Header.Get("Range")
|
contentRange := this.RawReq.Header.Get("Range")
|
||||||
if len(contentRange) > 0 {
|
if len(contentRange) > 0 {
|
||||||
@@ -249,36 +252,22 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
set, ok := httpRequestParseContentRange(contentRange)
|
set, ok := httpRequestParseRangeHeader(contentRange)
|
||||||
if !ok {
|
if !ok {
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(set) > 0 {
|
if len(set) > 0 {
|
||||||
rangeSet = set
|
ranges = set
|
||||||
for _, arr := range rangeSet {
|
for k, r := range ranges {
|
||||||
if arr[0] == -1 {
|
r2, ok := r.Convert(fileSize)
|
||||||
arr[0] = fileSize + arr[1]
|
if !ok {
|
||||||
arr[1] = fileSize - 1
|
|
||||||
|
|
||||||
if arr[0] < 0 {
|
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if arr[1] > 0 {
|
|
||||||
arr[1] = fileSize - 1
|
|
||||||
}
|
|
||||||
if arr[1] < 0 {
|
|
||||||
arr[1] = fileSize - 1
|
|
||||||
}
|
|
||||||
if arr[0] > arr[1] {
|
|
||||||
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable)
|
||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
ranges[k] = r2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -298,7 +287,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
this.processResponseHeaders(http.StatusOK)
|
this.processResponseHeaders(http.StatusOK)
|
||||||
|
|
||||||
// 在Range请求中不能缓存
|
// 在Range请求中不能缓存
|
||||||
if len(rangeSet) > 0 {
|
if len(ranges) > 0 {
|
||||||
this.cacheRef = nil // 不支持缓存
|
this.cacheRef = nil // 不支持缓存
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,11 +300,11 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
pool.Put(buf)
|
pool.Put(buf)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(rangeSet) == 1 {
|
if len(ranges) == 1 {
|
||||||
respHeader.Set("Content-Range", "bytes "+strconv.FormatInt(rangeSet[0][0], 10)+"-"+strconv.FormatInt(rangeSet[0][1], 10)+"/"+strconv.FormatInt(fileSize, 10))
|
respHeader.Set("Content-Range", ranges[0].ComposeContentRangeHeader(types.String(fileSize)))
|
||||||
this.writer.WriteHeader(http.StatusPartialContent)
|
this.writer.WriteHeader(http.StatusPartialContent)
|
||||||
|
|
||||||
ok, err := httpRequestReadRange(reader, buf, rangeSet[0][0], rangeSet[0][1], func(buf []byte, n int) error {
|
ok, err := httpRequestReadRange(reader, buf, ranges[0].Start(), ranges[0].End(), func(buf []byte, n int) error {
|
||||||
_, err := this.writer.Write(buf[:n])
|
_, err := this.writer.Write(buf[:n])
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -328,13 +317,13 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if len(rangeSet) > 1 {
|
} else if len(ranges) > 1 {
|
||||||
boundary := httpRequestGenBoundary()
|
boundary := httpRequestGenBoundary()
|
||||||
respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
|
respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
|
||||||
|
|
||||||
this.writer.WriteHeader(http.StatusPartialContent)
|
this.writer.WriteHeader(http.StatusPartialContent)
|
||||||
|
|
||||||
for index, set := range rangeSet {
|
for index, r := range ranges {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
_, err = this.writer.WriteString("--" + boundary + "\r\n")
|
_, err = this.writer.WriteString("--" + boundary + "\r\n")
|
||||||
} else {
|
} else {
|
||||||
@@ -345,7 +334,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = this.writer.WriteString("Content-Range: " + "bytes " + strconv.FormatInt(set[0], 10) + "-" + strconv.FormatInt(set[1], 10) + "/" + strconv.FormatInt(fileSize, 10) + "\r\n")
|
_, err = this.writer.WriteString("Content-Range: " + r.ComposeContentRangeHeader(types.String(fileSize)) + "\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Error(err)
|
logs.Error(err)
|
||||||
return true
|
return true
|
||||||
@@ -359,7 +348,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := httpRequestReadRange(reader, buf, set[0], set[1], func(buf []byte, n int) error {
|
ok, err := httpRequestReadRange(reader, buf, r.Start(), r.End(), func(buf []byte, n int) error {
|
||||||
_, err := this.writer.Write(buf[:n])
|
_, err := this.writer.Write(buf[:n])
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func TestHTTPRequest_RedirectToHTTPS(t *testing.T) {
|
|||||||
a := assert.NewAssertion(t)
|
a := assert.NewAssertion(t)
|
||||||
{
|
{
|
||||||
req := &HTTPRequest{
|
req := &HTTPRequest{
|
||||||
Server: &serverconfigs.ServerConfig{
|
ReqServer: &serverconfigs.ServerConfig{
|
||||||
Web: &serverconfigs.HTTPWebConfig{
|
Web: &serverconfigs.HTTPWebConfig{
|
||||||
RedirectToHttps: &serverconfigs.HTTPRedirectToHTTPSConfig{},
|
RedirectToHttps: &serverconfigs.HTTPRedirectToHTTPSConfig{},
|
||||||
},
|
},
|
||||||
@@ -22,7 +22,7 @@ func TestHTTPRequest_RedirectToHTTPS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
req := &HTTPRequest{
|
req := &HTTPRequest{
|
||||||
Server: &serverconfigs.ServerConfig{
|
ReqServer: &serverconfigs.ServerConfig{
|
||||||
Web: &serverconfigs.HTTPWebConfig{
|
Web: &serverconfigs.HTTPWebConfig{
|
||||||
RedirectToHttps: &serverconfigs.HTTPRedirectToHTTPSConfig{
|
RedirectToHttps: &serverconfigs.HTTPRedirectToHTTPSConfig{
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
|
|||||||
@@ -5,15 +5,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 其中的每个括号里的内容都在被引用,不能轻易修改
|
||||||
|
var contentRangeRegexp = regexp.MustCompile(`^bytes (\d+)-(\d+)/(\d+|\*)`)
|
||||||
|
|
||||||
// 分解Range
|
// 分解Range
|
||||||
func httpRequestParseContentRange(rangeValue string) (result [][]int64, ok bool) {
|
func httpRequestParseRangeHeader(rangeValue string) (result []rangeutils.Range, ok bool) {
|
||||||
// 参考RFC:https://tools.ietf.org/html/rfc7233
|
// 参考RFC:https://tools.ietf.org/html/rfc7233
|
||||||
index := strings.Index(rangeValue, "=")
|
index := strings.Index(rangeValue, "=")
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
@@ -24,15 +30,15 @@ func httpRequestParseContentRange(rangeValue string) (result [][]int64, ok bool)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rangeSetString := rangeValue[index+1:]
|
var rangeSetString = rangeValue[index+1:]
|
||||||
if len(rangeSetString) == 0 {
|
if len(rangeSetString) == 0 {
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pieces := strings.Split(rangeSetString, ", ")
|
var pieces = strings.Split(rangeSetString, ", ")
|
||||||
for _, piece := range pieces {
|
for _, piece := range pieces {
|
||||||
index := strings.Index(piece, "-")
|
index = strings.Index(piece, "-")
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -70,7 +76,7 @@ func httpRequestParseContentRange(rangeValue string) (result [][]int64, ok bool)
|
|||||||
lastInt = -lastInt
|
lastInt = -lastInt
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, []int64{firstInt, lastInt})
|
result = append(result, [2]int64{firstInt, lastInt})
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = true
|
ok = true
|
||||||
@@ -119,10 +125,25 @@ func httpRequestReadRange(reader io.Reader, buf []byte, start int64, end int64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分解Content-Range
|
||||||
|
func httpRequestParseContentRangeHeader(contentRange string) (start int64, total int64) {
|
||||||
|
var matches = contentRangeRegexp.FindStringSubmatch(contentRange)
|
||||||
|
if len(matches) < 4 {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
start = types.Int64(matches[1])
|
||||||
|
var sizeString = matches[3]
|
||||||
|
if sizeString != "*" {
|
||||||
|
total = types.Int64(sizeString)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 生成boundary
|
// 生成boundary
|
||||||
// 仿照Golang自带的函数(multipart包)
|
// 仿照Golang自带的函数(multipart包)
|
||||||
func httpRequestGenBoundary() string {
|
func httpRequestGenBoundary() string {
|
||||||
var buf [30]byte
|
var buf [8]byte
|
||||||
_, err := io.ReadFull(rand.Reader, buf[:])
|
_, err := io.ReadFull(rand.Reader, buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -130,6 +151,21 @@ func httpRequestGenBoundary() string {
|
|||||||
return fmt.Sprintf("%x", buf[:])
|
return fmt.Sprintf("%x", buf[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从content-type中读取boundary
|
||||||
|
func httpRequestParseBoundary(contentType string) string {
|
||||||
|
var delim = "boundary="
|
||||||
|
var boundaryIndex = strings.Index(contentType, delim)
|
||||||
|
if boundaryIndex < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var boundary = contentType[boundaryIndex+len(delim):]
|
||||||
|
semicolonIndex := strings.Index(boundary, ";")
|
||||||
|
if semicolonIndex >= 0 {
|
||||||
|
return boundary[:semicolonIndex]
|
||||||
|
}
|
||||||
|
return boundary
|
||||||
|
}
|
||||||
|
|
||||||
// 判断状态是否为跳转
|
// 判断状态是否为跳转
|
||||||
func httpStatusIsRedirect(statusCode int) bool {
|
func httpStatusIsRedirect(statusCode int) bool {
|
||||||
return statusCode == http.StatusPermanentRedirect ||
|
return statusCode == http.StatusPermanentRedirect ||
|
||||||
@@ -156,6 +192,9 @@ func httpRequestNextId() string {
|
|||||||
|
|
||||||
// 检查是否可以接受某个编码
|
// 检查是否可以接受某个编码
|
||||||
func httpAcceptEncoding(acceptEncodings string, encoding string) bool {
|
func httpAcceptEncoding(acceptEncodings string, encoding string) bool {
|
||||||
|
if len(acceptEncodings) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
var pieces = strings.Split(acceptEncodings, ",")
|
var pieces = strings.Split(acceptEncodings, ",")
|
||||||
for _, piece := range pieces {
|
for _, piece := range pieces {
|
||||||
var qualityIndex = strings.Index(piece, ";")
|
var qualityIndex = strings.Index(piece, ";")
|
||||||
@@ -169,3 +208,20 @@ func httpAcceptEncoding(acceptEncodings string, encoding string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分隔编码
|
||||||
|
func httpAcceptEncodings(acceptEncodings string) (encodings []string) {
|
||||||
|
if len(acceptEncodings) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pieces = strings.Split(acceptEncodings, ",")
|
||||||
|
for _, piece := range pieces {
|
||||||
|
var qualityIndex = strings.Index(piece, ";")
|
||||||
|
if qualityIndex >= 0 {
|
||||||
|
piece = piece[:qualityIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
encodings = append(encodings, strings.TrimSpace(piece))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,52 +10,91 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTTPRequest_httpRequestParseContentRange(t *testing.T) {
|
func TestHTTPRequest_httpRequestGenBoundary(t *testing.T) {
|
||||||
a := assert.NewAssertion(t)
|
for i := 0; i < 10; i++ {
|
||||||
|
var boundary = httpRequestGenBoundary()
|
||||||
|
t.Log(boundary, "[", len(boundary), "bytes", "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPRequest_httpRequestParseBoundary(t *testing.T) {
|
||||||
|
var a = assert.NewAssertion(t)
|
||||||
|
a.IsTrue(httpRequestParseBoundary("multipart/byteranges") == "")
|
||||||
|
a.IsTrue(httpRequestParseBoundary("multipart/byteranges; boundary=123") == "123")
|
||||||
|
a.IsTrue(httpRequestParseBoundary("multipart/byteranges; boundary=123; 456") == "123")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPRequest_httpRequestParseRangeHeader(t *testing.T) {
|
||||||
|
var a = assert.NewAssertion(t)
|
||||||
{
|
{
|
||||||
_, ok := httpRequestParseContentRange("")
|
_, ok := httpRequestParseRangeHeader("")
|
||||||
a.IsFalse(ok)
|
a.IsFalse(ok)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
_, ok := httpRequestParseContentRange("byte=")
|
_, ok := httpRequestParseRangeHeader("byte=")
|
||||||
a.IsFalse(ok)
|
a.IsFalse(ok)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
_, ok := httpRequestParseContentRange("byte=")
|
_, ok := httpRequestParseRangeHeader("byte=")
|
||||||
a.IsFalse(ok)
|
a.IsFalse(ok)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
set, ok := httpRequestParseContentRange("bytes=")
|
set, ok := httpRequestParseRangeHeader("bytes=")
|
||||||
a.IsTrue(ok)
|
a.IsTrue(ok)
|
||||||
a.IsTrue(len(set) == 0)
|
a.IsTrue(len(set) == 0)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
_, ok := httpRequestParseContentRange("bytes=60-50")
|
_, ok := httpRequestParseRangeHeader("bytes=60-50")
|
||||||
a.IsFalse(ok)
|
a.IsFalse(ok)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
set, ok := httpRequestParseContentRange("bytes=0-50")
|
set, ok := httpRequestParseRangeHeader("bytes=0-50")
|
||||||
a.IsTrue(ok)
|
a.IsTrue(ok)
|
||||||
a.IsTrue(len(set) > 0)
|
a.IsTrue(len(set) > 0)
|
||||||
t.Log(set)
|
t.Log(set)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
set, ok := httpRequestParseContentRange("bytes=0-")
|
set, ok := httpRequestParseRangeHeader("bytes=0-")
|
||||||
|
a.IsTrue(ok)
|
||||||
|
a.IsTrue(len(set) > 0)
|
||||||
|
if len(set) > 0 {
|
||||||
|
a.IsTrue(set[0][0] == 0)
|
||||||
|
}
|
||||||
|
t.Log(set)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
set, ok := httpRequestParseRangeHeader("bytes=-50")
|
||||||
a.IsTrue(ok)
|
a.IsTrue(ok)
|
||||||
a.IsTrue(len(set) > 0)
|
a.IsTrue(len(set) > 0)
|
||||||
t.Log(set)
|
t.Log(set)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
set, ok := httpRequestParseContentRange("bytes=-50")
|
set, ok := httpRequestParseRangeHeader("bytes=0-50, 60-100")
|
||||||
a.IsTrue(ok)
|
a.IsTrue(ok)
|
||||||
a.IsTrue(len(set) > 0)
|
a.IsTrue(len(set) > 0)
|
||||||
t.Log(set)
|
t.Log(set)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPRequest_httpRequestParseContentRangeHeader(t *testing.T) {
|
||||||
{
|
{
|
||||||
set, ok := httpRequestParseContentRange("bytes=0-50, 60-100")
|
var c1 = "bytes 0-100/*"
|
||||||
a.IsTrue(ok)
|
t.Log(httpRequestParseContentRangeHeader(c1))
|
||||||
a.IsTrue(len(set) > 0)
|
}
|
||||||
t.Log(set)
|
{
|
||||||
|
var c1 = "bytes 30-100/*"
|
||||||
|
t.Log(httpRequestParseContentRangeHeader(c1))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var c1 = "bytes1 0-100/*"
|
||||||
|
t.Log(httpRequestParseContentRangeHeader(c1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkHTTPRequest_httpRequestParseContentRangeHeader(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var c1 = "bytes 0-100/*"
|
||||||
|
httpRequestParseContentRangeHeader(c1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,11 +35,15 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 是否在全局名单中
|
// 是否在全局名单中
|
||||||
if !iplibrary.AllowIP(remoteAddr, this.ReqServer.Id) {
|
canGoNext, isInAllowedList := iplibrary.AllowIP(remoteAddr, this.ReqServer.Id)
|
||||||
|
if !canGoNext {
|
||||||
this.disableLog = true
|
this.disableLog = true
|
||||||
this.Close()
|
this.Close()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if isInAllowedList {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否在临时黑名单中
|
// 检查是否在临时黑名单中
|
||||||
if waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeService, this.ReqServer.Id, remoteAddr) || waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteAddr) {
|
if waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeService, this.ReqServer.Id, remoteAddr) || waf.SharedIPBlackList.Contains(waf.IPTypeAll, firewallconfigs.FirewallScopeGlobal, 0, remoteAddr) {
|
||||||
|
|||||||
@@ -28,15 +28,13 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// webp相关配置
|
|
||||||
const webpSuffix = "@GOEDGE_WEBP"
|
|
||||||
|
|
||||||
var webpMaxBufferSize int64 = 1_000_000_000
|
var webpMaxBufferSize int64 = 1_000_000_000
|
||||||
var webpTotalBufferSize int64 = 0
|
var webpTotalBufferSize int64 = 0
|
||||||
|
|
||||||
@@ -66,17 +64,25 @@ type HTTPWriter struct {
|
|||||||
isOk bool // 是否完全成功
|
isOk bool // 是否完全成功
|
||||||
isFinished bool // 是否已完成
|
isFinished bool // 是否已完成
|
||||||
|
|
||||||
|
// Partial
|
||||||
|
isPartial bool
|
||||||
|
partialFileIsNew bool
|
||||||
|
|
||||||
// WebP
|
// WebP
|
||||||
webpIsEncoding bool
|
webpIsEncoding bool
|
||||||
webpOriginContentType string
|
webpOriginContentType string
|
||||||
|
|
||||||
// Compression
|
// Compression
|
||||||
compressionConfig *serverconfigs.HTTPCompressionConfig
|
compressionConfig *serverconfigs.HTTPCompressionConfig
|
||||||
|
compressionCacheWriter caches.Writer
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
cacheStorage caches.StorageInterface
|
cacheStorage caches.StorageInterface
|
||||||
cacheWriter caches.Writer
|
cacheWriter caches.Writer
|
||||||
cacheIsFinished bool
|
cacheIsFinished bool
|
||||||
|
|
||||||
|
cacheReader caches.Reader
|
||||||
|
cacheReaderSuffix string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPWriter 包装对象
|
// NewHTTPWriter 包装对象
|
||||||
@@ -95,13 +101,28 @@ func (this *HTTPWriter) Prepare(resp *http.Response, size int64, status int, ena
|
|||||||
this.size = size
|
this.size = size
|
||||||
this.statusCode = status
|
this.statusCode = status
|
||||||
|
|
||||||
if resp != nil {
|
// 是否为区间请求
|
||||||
|
this.isPartial = status == http.StatusPartialContent
|
||||||
|
|
||||||
|
// 不支持对GET以外的方法返回的Partial内容的缓存
|
||||||
|
if this.isPartial && this.req.Method() != http.MethodGet {
|
||||||
|
enableCache = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil && resp.Body != nil {
|
||||||
|
cacheReader, ok := resp.Body.(caches.Reader)
|
||||||
|
if ok {
|
||||||
|
this.cacheReader = cacheReader
|
||||||
|
}
|
||||||
|
|
||||||
this.rawReader = resp.Body
|
this.rawReader = resp.Body
|
||||||
|
|
||||||
if enableCache {
|
if enableCache {
|
||||||
this.PrepareCache(resp, size)
|
this.PrepareCache(resp, size)
|
||||||
}
|
}
|
||||||
this.PrepareWebP(resp, size)
|
if !this.isPartial {
|
||||||
|
this.PrepareWebP(resp, size)
|
||||||
|
}
|
||||||
this.PrepareCompression(resp, size)
|
this.PrepareCompression(resp, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,12 +156,21 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
var addStatusHeader = this.req.web != nil && this.req.web.Cache != nil && this.req.web.Cache.AddStatusHeader
|
var addStatusHeader = this.req.web != nil && this.req.web.Cache != nil && this.req.web.Cache.AddStatusHeader
|
||||||
|
|
||||||
// 不支持Range
|
// 不支持Range
|
||||||
if len(this.Header().Get("Content-Range")) > 0 {
|
if this.isPartial {
|
||||||
this.req.varMapping["cache.status"] = "BYPASS"
|
if !cacheRef.AllowPartialContent {
|
||||||
if addStatusHeader {
|
this.req.varMapping["cache.status"] = "BYPASS"
|
||||||
this.Header().Set("X-Cache", "BYPASS, not supported Content-Range")
|
if addStatusHeader {
|
||||||
|
this.Header().Set("X-Cache", "BYPASS, not supported partial content")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if this.cacheStorage.Policy().Type != serverconfigs.CachePolicyStorageFile {
|
||||||
|
this.req.varMapping["cache.status"] = "BYPASS"
|
||||||
|
if addStatusHeader {
|
||||||
|
this.Header().Set("X-Cache", "BYPASS, not supported partial content in memory storage")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果允许 ChunkedEncoding,就无需尺寸的判断,因为此时的 size 为 -1
|
// 如果允许 ChunkedEncoding,就无需尺寸的判断,因为此时的 size 为 -1
|
||||||
@@ -151,8 +181,18 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if size >= 0 && ((cacheRef.MaxSizeBytes() > 0 && size > cacheRef.MaxSizeBytes()) ||
|
|
||||||
(cachePolicy.MaxSizeBytes() > 0 && size > cachePolicy.MaxSizeBytes()) || (cacheRef.MinSizeBytes() > size)) {
|
var contentSize = size
|
||||||
|
if this.isPartial {
|
||||||
|
// 从Content-Range中读取内容总长度
|
||||||
|
var contentRange = this.Header().Get("Content-Range")
|
||||||
|
_, totalSize := httpRequestParseContentRangeHeader(contentRange)
|
||||||
|
if totalSize > 0 {
|
||||||
|
contentSize = totalSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if contentSize >= 0 && ((cacheRef.MaxSizeBytes() > 0 && contentSize > cacheRef.MaxSizeBytes()) ||
|
||||||
|
(cachePolicy.MaxSizeBytes() > 0 && contentSize > cachePolicy.MaxSizeBytes()) || (cacheRef.MinSizeBytes() > contentSize)) {
|
||||||
this.req.varMapping["cache.status"] = "BYPASS"
|
this.req.varMapping["cache.status"] = "BYPASS"
|
||||||
if addStatusHeader {
|
if addStatusHeader {
|
||||||
this.Header().Set("X-Cache", "BYPASS, Content-Length")
|
this.Header().Set("X-Cache", "BYPASS, Content-Length")
|
||||||
@@ -161,7 +201,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查状态
|
// 检查状态
|
||||||
if len(cacheRef.Status) > 0 && !lists.ContainsInt(cacheRef.Status, this.StatusCode()) {
|
if !cacheRef.MatchStatus(this.StatusCode()) {
|
||||||
this.req.varMapping["cache.status"] = "BYPASS"
|
this.req.varMapping["cache.status"] = "BYPASS"
|
||||||
if addStatusHeader {
|
if addStatusHeader {
|
||||||
this.Header().Set("X-Cache", "BYPASS, Status: "+types.String(this.StatusCode()))
|
this.Header().Set("X-Cache", "BYPASS, Status: "+types.String(this.StatusCode()))
|
||||||
@@ -171,7 +211,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
|
|
||||||
// Cache-Control
|
// Cache-Control
|
||||||
if len(cacheRef.SkipResponseCacheControlValues) > 0 {
|
if len(cacheRef.SkipResponseCacheControlValues) > 0 {
|
||||||
var cacheControl = this.Header().Get("Cache-Control")
|
var cacheControl = this.GetHeader("Cache-Control")
|
||||||
if len(cacheControl) > 0 {
|
if len(cacheControl) > 0 {
|
||||||
values := strings.Split(cacheControl, ",")
|
values := strings.Split(cacheControl, ",")
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
@@ -187,7 +227,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set-Cookie
|
// Set-Cookie
|
||||||
if cacheRef.SkipResponseSetCookie && len(this.Header().Get("Set-Cookie")) > 0 {
|
if cacheRef.SkipResponseSetCookie && len(this.GetHeader("Set-Cookie")) > 0 {
|
||||||
this.req.varMapping["cache.status"] = "BYPASS"
|
this.req.varMapping["cache.status"] = "BYPASS"
|
||||||
if addStatusHeader {
|
if addStatusHeader {
|
||||||
this.Header().Set("X-Cache", "BYPASS, Set-Cookie")
|
this.Header().Set("X-Cache", "BYPASS, Set-Cookie")
|
||||||
@@ -220,7 +260,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.cacheStorage = storage
|
this.cacheStorage = storage
|
||||||
life := cacheRef.LifeSeconds()
|
var life = cacheRef.LifeSeconds()
|
||||||
|
|
||||||
if life <= 0 {
|
if life <= 0 {
|
||||||
life = 60
|
life = 60
|
||||||
@@ -228,7 +268,7 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
|
|
||||||
// 支持源站设置的max-age
|
// 支持源站设置的max-age
|
||||||
if this.req.web.Cache != nil && this.req.web.Cache.EnableCacheControlMaxAge {
|
if this.req.web.Cache != nil && this.req.web.Cache.EnableCacheControlMaxAge {
|
||||||
var cacheControl = this.Header().Get("Cache-Control")
|
var cacheControl = this.GetHeader("Cache-Control")
|
||||||
var pieces = strings.Split(cacheControl, ";")
|
var pieces = strings.Split(cacheControl, ";")
|
||||||
for _, piece := range pieces {
|
for _, piece := range pieces {
|
||||||
var eqIndex = strings.Index(piece, "=")
|
var eqIndex = strings.Index(piece, "=")
|
||||||
@@ -243,8 +283,15 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
|
|
||||||
var expiredAt = utils.UnixTime() + life
|
var expiredAt = utils.UnixTime() + life
|
||||||
var cacheKey = this.req.cacheKey
|
var cacheKey = this.req.cacheKey
|
||||||
cacheWriter, err := storage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), size, false)
|
if this.isPartial {
|
||||||
|
cacheKey += caches.SuffixPartial
|
||||||
|
}
|
||||||
|
cacheWriter, err := storage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), size, cacheRef.MaxSizeBytes(), this.isPartial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == caches.ErrEntityTooLarge && addStatusHeader {
|
||||||
|
this.Header().Set("X-Cache", "BYPASS, entity too large")
|
||||||
|
}
|
||||||
|
|
||||||
if !caches.CanIgnoreErr(err) {
|
if !caches.CanIgnoreErr(err) {
|
||||||
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
||||||
}
|
}
|
||||||
@@ -252,9 +299,16 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
this.cacheWriter = cacheWriter
|
this.cacheWriter = cacheWriter
|
||||||
|
|
||||||
|
if this.isPartial {
|
||||||
|
this.partialFileIsNew = cacheWriter.(*caches.PartialFileWriter).IsNew()
|
||||||
|
}
|
||||||
|
|
||||||
// 写入Header
|
// 写入Header
|
||||||
for k, v := range this.Header() {
|
for k, v := range this.Header() {
|
||||||
for _, v1 := range v {
|
for _, v1 := range v {
|
||||||
|
if this.isPartial && k == "Content-Type" && strings.Contains(v1, "multipart/byteranges") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_, err = cacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
_, err = cacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error())
|
||||||
@@ -265,6 +319,110 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if this.isPartial {
|
||||||
|
// content-range
|
||||||
|
var contentRange = this.GetHeader("Content-Range")
|
||||||
|
if len(contentRange) > 0 {
|
||||||
|
start, total := httpRequestParseContentRangeHeader(contentRange)
|
||||||
|
if start < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if total > 0 {
|
||||||
|
partialWriter, ok := cacheWriter.(*caches.PartialFileWriter)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
partialWriter.SetBodyLength(total)
|
||||||
|
}
|
||||||
|
var filterReader = readers.NewFilterReaderCloser(resp.Body)
|
||||||
|
this.cacheIsFinished = true
|
||||||
|
var hasError = false
|
||||||
|
filterReader.Add(func(p []byte, err error) error {
|
||||||
|
if hasError {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l = len(p)
|
||||||
|
if l == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
start += int64(l)
|
||||||
|
}()
|
||||||
|
err = cacheWriter.WriteAt(start, p)
|
||||||
|
if err != nil {
|
||||||
|
this.cacheIsFinished = false
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
resp.Body = filterReader
|
||||||
|
this.rawReader = filterReader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// multipart/byteranges
|
||||||
|
var contentType = this.GetHeader("Content-Type")
|
||||||
|
if strings.Contains(contentType, "multipart/byteranges") {
|
||||||
|
partialWriter, ok := cacheWriter.(*caches.PartialFileWriter)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var boundary = httpRequestParseBoundary(contentType)
|
||||||
|
if len(boundary) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader = readers.NewByteRangesReaderCloser(resp.Body, boundary)
|
||||||
|
var contentTypeWritten = false
|
||||||
|
|
||||||
|
this.cacheIsFinished = true
|
||||||
|
var hasError = false
|
||||||
|
var writtenTotal = false
|
||||||
|
reader.OnPartRead(func(start int64, end int64, total int64, data []byte, header textproto.MIMEHeader) {
|
||||||
|
// TODO 如果 total 超出缓存限制,则不写入缓存数据,并且记录到某个内存表中,下次不再OpenWriter
|
||||||
|
|
||||||
|
if hasError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入total
|
||||||
|
if !writtenTotal && total > 0 {
|
||||||
|
partialWriter.SetBodyLength(total)
|
||||||
|
writtenTotal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入Content-Type
|
||||||
|
if partialWriter.IsNew() && !contentTypeWritten {
|
||||||
|
var realContentType = header.Get("Content-Type")
|
||||||
|
if len(realContentType) > 0 {
|
||||||
|
var h = []byte("Content-Type:" + realContentType + "\n")
|
||||||
|
err = partialWriter.AppendHeader(h)
|
||||||
|
if err != nil {
|
||||||
|
hasError = true
|
||||||
|
this.cacheIsFinished = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentTypeWritten = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cacheWriter.WriteAt(start, data)
|
||||||
|
if err != nil {
|
||||||
|
hasError = true
|
||||||
|
this.cacheIsFinished = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resp.Body = reader
|
||||||
|
this.rawReader = reader
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var cacheReader = readers.NewTeeReaderCloser(resp.Body, this.cacheWriter)
|
var cacheReader = readers.NewTeeReaderCloser(resp.Body, this.cacheWriter)
|
||||||
resp.Body = cacheReader
|
resp.Body = cacheReader
|
||||||
this.rawReader = cacheReader
|
this.rawReader = cacheReader
|
||||||
@@ -284,7 +442,7 @@ func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentType = this.Header().Get("Content-Type")
|
var contentType = this.GetHeader("Content-Type")
|
||||||
|
|
||||||
if this.req.web != nil &&
|
if this.req.web != nil &&
|
||||||
this.req.web.WebP != nil &&
|
this.req.web.WebP != nil &&
|
||||||
@@ -302,7 +460,7 @@ func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentEncoding = resp.Header.Get("Content-Encoding")
|
var contentEncoding = this.GetHeader("Content-Encoding")
|
||||||
switch contentEncoding {
|
switch contentEncoding {
|
||||||
case "gzip", "deflate", "br":
|
case "gzip", "deflate", "br":
|
||||||
reader, err := compressions.NewReader(resp.Body, contentEncoding)
|
reader, err := compressions.NewReader(resp.Body, contentEncoding)
|
||||||
@@ -329,8 +487,17 @@ func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) {
|
|||||||
|
|
||||||
// PrepareCompression 准备压缩
|
// PrepareCompression 准备压缩
|
||||||
func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
||||||
|
var method = this.req.Method()
|
||||||
|
if method == http.MethodHead {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.StatusCode() == http.StatusNoContent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var acceptEncodings = this.req.RawReq.Header.Get("Accept-Encoding")
|
var acceptEncodings = this.req.RawReq.Header.Get("Accept-Encoding")
|
||||||
var contentEncoding = this.Header().Get("Content-Encoding")
|
var contentEncoding = this.GetHeader("Content-Encoding")
|
||||||
|
|
||||||
if this.compressionConfig == nil || !this.compressionConfig.IsOn {
|
if this.compressionConfig == nil || !this.compressionConfig.IsOn {
|
||||||
if lists.ContainsString([]string{"gzip", "deflate", "br"}, contentEncoding) && !httpAcceptEncoding(acceptEncodings, contentEncoding) {
|
if lists.ContainsString([]string{"gzip", "deflate", "br"}, contentEncoding) && !httpAcceptEncoding(acceptEncodings, contentEncoding) {
|
||||||
@@ -355,11 +522,7 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 尺寸和类型
|
// 尺寸和类型
|
||||||
var contentType = this.Header().Get("Content-Type")
|
var contentType = this.GetHeader("Content-Type")
|
||||||
if len(contentType) == 0 {
|
|
||||||
// 如果没有显式设置Content-Type,我们就认为是 text/html
|
|
||||||
contentType = "text/html"
|
|
||||||
}
|
|
||||||
if !this.compressionConfig.MatchResponse(contentType, size, filepath.Ext(this.req.Path()), this.req.Format) {
|
if !this.compressionConfig.MatchResponse(contentType, size, filepath.Ext(this.req.Path()), this.req.Format) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -389,19 +552,76 @@ func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) {
|
|||||||
resp.Body = reader
|
resp.Body = reader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 需要放在compression cache writer之前
|
||||||
|
var header = this.rawWriter.Header()
|
||||||
|
header.Set("Content-Encoding", compressionEncoding)
|
||||||
|
header.Set("Vary", "Accept-Encoding")
|
||||||
|
header.Del("Content-Length")
|
||||||
|
|
||||||
|
// compression cache writer
|
||||||
|
// 只有在本身内容已经缓存的情况下才会写入缓存,防止同时写入缓存导致IO负载升高
|
||||||
|
var cacheRef = this.req.cacheRef
|
||||||
|
if !this.isPartial &&
|
||||||
|
this.cacheStorage != nil &&
|
||||||
|
cacheRef != nil &&
|
||||||
|
(this.cacheReader != nil || (this.cacheStorage.Policy().SyncCompressionCache && this.cacheWriter != nil)) &&
|
||||||
|
!this.webpIsEncoding {
|
||||||
|
var cacheKey = ""
|
||||||
|
var expiredAt int64 = 0
|
||||||
|
|
||||||
|
if this.cacheReader != nil {
|
||||||
|
cacheKey = this.req.cacheKey
|
||||||
|
expiredAt = this.cacheReader.ExpiresAt()
|
||||||
|
} else if this.cacheWriter != nil {
|
||||||
|
cacheKey = this.cacheWriter.Key()
|
||||||
|
expiredAt = this.cacheWriter.ExpiredAt()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(this.cacheReaderSuffix) > 0 {
|
||||||
|
cacheKey += this.cacheReaderSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
compressionCacheWriter, err := this.cacheStorage.OpenWriter(cacheKey+caches.SuffixCompression+compressionEncoding, expiredAt, this.StatusCode(), -1, cacheRef.MaxSizeBytes(), false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入Header
|
||||||
|
for k, v := range this.Header() {
|
||||||
|
for _, v1 := range v {
|
||||||
|
_, err = compressionCacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error())
|
||||||
|
_ = compressionCacheWriter.Discard()
|
||||||
|
compressionCacheWriter = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if compressionCacheWriter != nil {
|
||||||
|
this.compressionCacheWriter = compressionCacheWriter
|
||||||
|
var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter)
|
||||||
|
teeWriter.OnFail(func(err error) {
|
||||||
|
_ = compressionCacheWriter.Discard()
|
||||||
|
this.compressionCacheWriter = nil
|
||||||
|
})
|
||||||
|
this.writer = teeWriter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// compression writer
|
// compression writer
|
||||||
var err error = nil
|
var err error = nil
|
||||||
compressionWriter, err := compressions.NewWriter(this.writer, compressionType, int(this.compressionConfig.Level))
|
compressionWriter, err := compressions.NewWriter(this.writer, compressionType, int(this.compressionConfig.Level))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remotelogs.Error("HTTP_WRITER", err.Error())
|
remotelogs.Error("HTTP_WRITER", err.Error())
|
||||||
|
header.Del("Content-Encoding")
|
||||||
|
if this.compressionCacheWriter != nil {
|
||||||
|
_ = this.compressionCacheWriter.Discard()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.writer = compressionWriter
|
this.writer = compressionWriter
|
||||||
|
|
||||||
header := this.rawWriter.Header()
|
|
||||||
header.Set("Content-Encoding", compressionEncoding)
|
|
||||||
header.Set("Vary", "Accept-Encoding")
|
|
||||||
header.Del("Content-Length")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCompression 设置内容压缩配置
|
// SetCompression 设置内容压缩配置
|
||||||
@@ -422,6 +642,11 @@ func (this *HTTPWriter) Header() http.Header {
|
|||||||
return this.rawWriter.Header()
|
return this.rawWriter.Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHeader 读取Header值
|
||||||
|
func (this *HTTPWriter) GetHeader(name string) string {
|
||||||
|
return this.Header().Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteHeader 删除Header
|
// DeleteHeader 删除Header
|
||||||
func (this *HTTPWriter) DeleteHeader(name string) {
|
func (this *HTTPWriter) DeleteHeader(name string) {
|
||||||
this.rawWriter.Header().Del(name)
|
this.rawWriter.Header().Del(name)
|
||||||
@@ -557,13 +782,26 @@ func (this *HTTPWriter) Close() {
|
|||||||
var webpCacheWriter caches.Writer
|
var webpCacheWriter caches.Writer
|
||||||
|
|
||||||
// 准备WebP Cache
|
// 准备WebP Cache
|
||||||
if this.cacheWriter != nil {
|
if this.cacheReader != nil || this.cacheWriter != nil {
|
||||||
var cacheKey = this.cacheWriter.Key() + webpSuffix
|
var cacheKey = ""
|
||||||
|
var expiredAt int64 = 0
|
||||||
|
|
||||||
webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, this.cacheWriter.ExpiredAt(), this.StatusCode(), -1, false)
|
if this.cacheReader != nil {
|
||||||
|
cacheKey = this.req.cacheKey + caches.SuffixWebP
|
||||||
|
expiredAt = this.cacheReader.ExpiresAt()
|
||||||
|
} else if this.cacheWriter != nil {
|
||||||
|
cacheKey = this.cacheWriter.Key() + caches.SuffixWebP
|
||||||
|
expiredAt = this.cacheWriter.ExpiredAt()
|
||||||
|
}
|
||||||
|
|
||||||
|
webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), -1, -1, false)
|
||||||
if webpCacheWriter != nil {
|
if webpCacheWriter != nil {
|
||||||
// 写入Header
|
// 写入Header
|
||||||
for k, v := range this.Header() {
|
for k, v := range this.Header() {
|
||||||
|
// 这里是原始的数据,不需要内容编码
|
||||||
|
if k == "Content-Encoding" || k == "Transfer-Encoding" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, v1 := range v {
|
for _, v1 := range v {
|
||||||
_, err := webpCacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
_, err := webpCacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -680,19 +918,45 @@ func (this *HTTPWriter) Close() {
|
|||||||
// 缓存
|
// 缓存
|
||||||
if this.cacheWriter != nil {
|
if this.cacheWriter != nil {
|
||||||
if this.isOk && this.cacheIsFinished {
|
if this.isOk && this.cacheIsFinished {
|
||||||
// 对比Content-Length
|
// 对比缓存前后的Content-Length
|
||||||
var contentLengthString = this.Header().Get("Content-Length")
|
var method = this.req.Method()
|
||||||
if len(contentLengthString) > 0 {
|
if method != http.MethodHead && this.StatusCode() != http.StatusNoContent && !this.isPartial {
|
||||||
var contentLength = types.Int64(contentLengthString)
|
var contentLengthString = this.GetHeader("Content-Length")
|
||||||
if contentLength != this.cacheWriter.BodySize() {
|
if len(contentLengthString) > 0 {
|
||||||
this.isOk = false
|
var contentLength = types.Int64(contentLengthString)
|
||||||
_ = this.cacheWriter.Discard()
|
if contentLength != this.cacheWriter.BodySize() {
|
||||||
|
this.isOk = false
|
||||||
|
_ = this.cacheWriter.Discard()
|
||||||
|
this.cacheWriter = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if this.isOk {
|
if this.isOk && this.cacheWriter != nil {
|
||||||
err := this.cacheWriter.Close()
|
err := this.cacheWriter.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if !this.isPartial || this.partialFileIsNew {
|
||||||
|
var expiredAt = this.cacheWriter.ExpiredAt()
|
||||||
|
this.cacheStorage.AddToList(&caches.Item{
|
||||||
|
Type: this.cacheWriter.ItemType(),
|
||||||
|
Key: this.cacheWriter.Key(),
|
||||||
|
ExpiredAt: expiredAt,
|
||||||
|
StaleAt: expiredAt + int64(this.calculateStaleLife()),
|
||||||
|
HeaderSize: this.cacheWriter.HeaderSize(),
|
||||||
|
BodySize: this.cacheWriter.BodySize(),
|
||||||
|
Host: this.req.ReqHost,
|
||||||
|
ServerId: this.req.ReqServer.Id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !this.isPartial || !this.cacheIsFinished {
|
||||||
|
_ = this.cacheWriter.Discard()
|
||||||
|
} else {
|
||||||
|
// Partial的文件内容不删除
|
||||||
|
err := this.cacheWriter.Close()
|
||||||
|
if err == nil && this.partialFileIsNew {
|
||||||
var expiredAt = this.cacheWriter.ExpiredAt()
|
var expiredAt = this.cacheWriter.ExpiredAt()
|
||||||
this.cacheStorage.AddToList(&caches.Item{
|
this.cacheStorage.AddToList(&caches.Item{
|
||||||
Type: this.cacheWriter.ItemType(),
|
Type: this.cacheWriter.ItemType(),
|
||||||
@@ -706,8 +970,27 @@ func (this *HTTPWriter) Close() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.compressionCacheWriter != nil {
|
||||||
|
if this.isOk {
|
||||||
|
err := this.compressionCacheWriter.Close()
|
||||||
|
if err == nil {
|
||||||
|
var expiredAt = this.compressionCacheWriter.ExpiredAt()
|
||||||
|
this.cacheStorage.AddToList(&caches.Item{
|
||||||
|
Type: this.compressionCacheWriter.ItemType(),
|
||||||
|
Key: this.compressionCacheWriter.Key(),
|
||||||
|
ExpiredAt: expiredAt,
|
||||||
|
StaleAt: expiredAt + int64(this.calculateStaleLife()),
|
||||||
|
HeaderSize: this.compressionCacheWriter.HeaderSize(),
|
||||||
|
BodySize: this.compressionCacheWriter.BodySize(),
|
||||||
|
Host: this.req.ReqHost,
|
||||||
|
ServerId: this.req.ReqServer.Id,
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = this.cacheWriter.Discard()
|
_ = this.compressionCacheWriter.Discard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,7 +1027,7 @@ func (this *HTTPWriter) calculateStaleLife() int {
|
|||||||
// 从Header中读取stale-if-error
|
// 从Header中读取stale-if-error
|
||||||
var isDefinedInHeader = false
|
var isDefinedInHeader = false
|
||||||
if staleConfig.SupportStaleIfErrorHeader {
|
if staleConfig.SupportStaleIfErrorHeader {
|
||||||
var cacheControl = this.Header().Get("Cache-Control")
|
var cacheControl = this.GetHeader("Cache-Control")
|
||||||
var pieces = strings.Split(cacheControl, ",")
|
var pieces = strings.Split(cacheControl, ",")
|
||||||
for _, piece := range pieces {
|
for _, piece := range pieces {
|
||||||
var eqIndex = strings.Index(piece, "=")
|
var eqIndex = strings.Index(piece, "=")
|
||||||
|
|||||||
@@ -34,22 +34,27 @@ func (this *BaseListener) CountActiveConnections() int {
|
|||||||
func (this *BaseListener) buildTLSConfig() *tls.Config {
|
func (this *BaseListener) buildTLSConfig() *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
Certificates: nil,
|
Certificates: nil,
|
||||||
GetConfigForClient: func(configInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
GetConfigForClient: func(clientInfo *tls.ClientHelloInfo) (config *tls.Config, e error) {
|
||||||
ssl, _, err := this.matchSSL(configInfo.ServerName)
|
tlsPolicy, _, err := this.matchSSL(clientInfo.ServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssl.TLSConfig(), nil
|
tlsPolicy.CheckOCSP()
|
||||||
|
|
||||||
|
return tlsPolicy.TLSConfig(), nil
|
||||||
},
|
},
|
||||||
GetCertificate: func(certInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
GetCertificate: func(clientInfo *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
|
||||||
_, cert, err := this.matchSSL(certInfo.ServerName)
|
tlsPolicy, cert, err := this.matchSSL(clientInfo.ServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return nil, errors.New("no ssl certs found for '" + certInfo.ServerName + "'")
|
return nil, errors.New("no ssl certs found for '" + clientInfo.ServerName + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsPolicy.CheckOCSP()
|
||||||
|
|
||||||
return cert, nil
|
return cert, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,21 +22,23 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/andybalholm/brotli"
|
"github.com/andybalholm/brotli"
|
||||||
"github.com/go-yaml/yaml"
|
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
"github.com/iwind/gosock/pkg/gosock"
|
"github.com/iwind/gosock/pkg/gosock"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,6 +98,9 @@ func (this *Node) Start() {
|
|||||||
// 处理异常
|
// 处理异常
|
||||||
this.handlePanic()
|
this.handlePanic()
|
||||||
|
|
||||||
|
// 监听signal
|
||||||
|
this.listenSignals()
|
||||||
|
|
||||||
// 启动事件
|
// 启动事件
|
||||||
events.Notify(events.EventStart)
|
events.Notify(events.EventStart)
|
||||||
|
|
||||||
@@ -593,6 +598,19 @@ func (this *Node) checkClusterConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听一些信号
|
||||||
|
func (this *Node) listenSignals() {
|
||||||
|
var queue = make(chan os.Signal, 8)
|
||||||
|
signal.Notify(queue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
|
||||||
|
goman.New(func() {
|
||||||
|
for range queue {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
utils.Exit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 监听本地sock
|
// 监听本地sock
|
||||||
func (this *Node) listenSock() error {
|
func (this *Node) listenSock() error {
|
||||||
// 检查是否在运行
|
// 检查是否在运行
|
||||||
@@ -631,7 +649,7 @@ func (this *Node) listenSock() error {
|
|||||||
|
|
||||||
// 退出主进程
|
// 退出主进程
|
||||||
events.Notify(events.EventQuit)
|
events.Notify(events.EventQuit)
|
||||||
os.Exit(0)
|
utils.Exit()
|
||||||
case "quit":
|
case "quit":
|
||||||
_ = cmd.ReplyOk()
|
_ = cmd.ReplyOk()
|
||||||
_ = this.sock.Close()
|
_ = this.sock.Close()
|
||||||
@@ -643,7 +661,7 @@ func (this *Node) listenSock() error {
|
|||||||
for {
|
for {
|
||||||
countActiveConnections := sharedListenerManager.TotalActiveConnections()
|
countActiveConnections := sharedListenerManager.TotalActiveConnections()
|
||||||
if countActiveConnections <= 0 {
|
if countActiveConnections <= 0 {
|
||||||
os.Exit(0)
|
utils.Exit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
@@ -742,6 +760,17 @@ func (this *Node) listenSock() error {
|
|||||||
runtime.GC()
|
runtime.GC()
|
||||||
debug.FreeOSMemory()
|
debug.FreeOSMemory()
|
||||||
_ = cmd.ReplyOk()
|
_ = cmd.ReplyOk()
|
||||||
|
case "reload":
|
||||||
|
err := this.syncConfig(0)
|
||||||
|
if err != nil {
|
||||||
|
_ = cmd.Reply(&gosock.Command{
|
||||||
|
Params: map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
_ = cmd.ReplyOk()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -767,6 +796,7 @@ func (this *Node) onReload(config *nodeconfigs.NodeConfig) {
|
|||||||
// 缓存策略
|
// 缓存策略
|
||||||
caches.SharedManager.MaxDiskCapacity = config.MaxCacheDiskCapacity
|
caches.SharedManager.MaxDiskCapacity = config.MaxCacheDiskCapacity
|
||||||
caches.SharedManager.MaxMemoryCapacity = config.MaxCacheMemoryCapacity
|
caches.SharedManager.MaxMemoryCapacity = config.MaxCacheMemoryCapacity
|
||||||
|
caches.SharedManager.DiskDir = config.CacheDiskDir
|
||||||
if len(config.HTTPCachePolicies) > 0 {
|
if len(config.HTTPCachePolicies) > 0 {
|
||||||
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies)
|
caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/disk"
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package nodes
|
package nodes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
"github.com/shirou/gopsutil/load"
|
"github.com/shirou/gopsutil/v3/load"
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 更新内存
|
// 更新内存
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ package nodes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|||||||
86
internal/nodes/task_ocsp_update.go
Normal file
86
internal/nodes/task_ocsp_update.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedOCSPTask = NewOCSPUpdateTask()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
events.On(events.EventLoaded, func() {
|
||||||
|
sharedOCSPTask.version = sharedNodeConfig.OCSPVersion
|
||||||
|
|
||||||
|
goman.New(func() {
|
||||||
|
sharedOCSPTask.Start()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
events.On(events.EventQuit, func() {
|
||||||
|
sharedOCSPTask.Stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPUpdateTask 更新OCSP任务
|
||||||
|
type OCSPUpdateTask struct {
|
||||||
|
version int64
|
||||||
|
|
||||||
|
ticker *time.Ticker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOCSPUpdateTask() *OCSPUpdateTask {
|
||||||
|
var ticker = time.NewTicker(1 * time.Minute)
|
||||||
|
if Tea.IsTesting() {
|
||||||
|
ticker = time.NewTicker(10 * time.Second)
|
||||||
|
}
|
||||||
|
return &OCSPUpdateTask{
|
||||||
|
ticker: ticker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *OCSPUpdateTask) Start() {
|
||||||
|
for range this.ticker.C {
|
||||||
|
err := this.Loop()
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Warn("OCSPUpdateTask", "update ocsp failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *OCSPUpdateTask) Loop() error {
|
||||||
|
rpcClient, err := rpc.SharedRPC()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcClient.SSLCertService().ListUpdatedSSLCertOCSP(rpcClient.Context(), &pb.ListUpdatedSSLCertOCSPRequest{
|
||||||
|
Version: this.version,
|
||||||
|
Size: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ocsp := range resp.SslCertOCSP {
|
||||||
|
// 更新OCSP
|
||||||
|
if sharedNodeConfig != nil {
|
||||||
|
sharedNodeConfig.UpdateCertOCSP(ocsp.SslCertId, ocsp.Data, ocsp.ExpiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改版本
|
||||||
|
this.version = ocsp.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *OCSPUpdateTask) Stop() {
|
||||||
|
this.ticker.Stop()
|
||||||
|
}
|
||||||
16
internal/nodes/task_ocsp_update_test.go
Normal file
16
internal/nodes/task_ocsp_update_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package nodes_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/nodes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOCSPUpdateTask_Loop(t *testing.T) {
|
||||||
|
var task = &nodes.OCSPUpdateTask{}
|
||||||
|
err := task.Loop()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -228,7 +228,7 @@ func (this *UpgradeManager) unzip(zipPath string) error {
|
|||||||
func (this *UpgradeManager) restart() error {
|
func (this *UpgradeManager) restart() error {
|
||||||
// 重新启动
|
// 重新启动
|
||||||
if DaemonIsOn && DaemonPid == os.Getppid() {
|
if DaemonIsOn && DaemonPid == os.Getppid() {
|
||||||
os.Exit(0) // TODO 试着更优雅重启
|
utils.Exit() // TODO 试着更优雅重启
|
||||||
} else {
|
} else {
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -238,6 +238,9 @@ func (this *UpgradeManager) restart() error {
|
|||||||
// quit
|
// quit
|
||||||
events.Notify(events.EventQuit)
|
events.Notify(events.EventQuit)
|
||||||
|
|
||||||
|
// terminated
|
||||||
|
events.Notify(events.EventTerminated)
|
||||||
|
|
||||||
// 启动
|
// 启动
|
||||||
cmd := exec.Command(exe, "start")
|
cmd := exec.Command(exe, "start")
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ package re
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"regexp/syntax"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var prefixReg = regexp.MustCompile(`^\(\?([\w\s]+)\)`) // (?x)
|
var prefixReg = regexp.MustCompile(`^\(\?([\w\s]+)\)`) // (?x)
|
||||||
var prefixReg2 = regexp.MustCompile(`^\(\?([\w\s]*:)`) // (?x: ...
|
var prefixReg2 = regexp.MustCompile(`^\(\?([\w\s]*:)`) // (?x: ...
|
||||||
var braceZero = regexp.MustCompile(`^{\s*0*\s*}`) // {0}
|
var braceZeroReg = regexp.MustCompile(`^{\s*0*\s*}`) // {0}
|
||||||
var braceZero2 = regexp.MustCompile(`^{\s*0*\s*,`) // {0, x}
|
var braceZeroReg2 = regexp.MustCompile(`^{\s*0*\s*,`) // {0, x}
|
||||||
|
|
||||||
type Regexp struct {
|
type Regexp struct {
|
||||||
exp string
|
exp string
|
||||||
@@ -53,8 +54,6 @@ func (this *Regexp) init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//var keywords = []string{}
|
|
||||||
|
|
||||||
var exp = strings.TrimSpace(this.exp)
|
var exp = strings.TrimSpace(this.exp)
|
||||||
|
|
||||||
// 去掉前面的(?...)
|
// 去掉前面的(?...)
|
||||||
@@ -68,9 +67,23 @@ func (this *Regexp) init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keywords = this.ParseKeywords(exp)
|
var keywords = this.ParseKeywords(exp)
|
||||||
this.keywords = keywords
|
|
||||||
if len(keywords) > 0 {
|
var filteredKeywords = []string{}
|
||||||
this.keywordsMap = NewRuneTree(keywords)
|
var minLength = 1
|
||||||
|
var isValid = true
|
||||||
|
for _, keyword := range keywords {
|
||||||
|
if len(keyword) <= minLength {
|
||||||
|
isValid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isValid {
|
||||||
|
filteredKeywords = keywords
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keywords = filteredKeywords
|
||||||
|
if len(filteredKeywords) > 0 {
|
||||||
|
this.keywordsMap = NewRuneTree(filteredKeywords)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +109,7 @@ func (this *Regexp) MatchString(s string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.rawRegexp.MatchString(s)
|
return this.rawRegexp.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,104 +127,106 @@ func (this *Regexp) Match(s []byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseKeywords 提取表达式中的关键词
|
// ParseKeywords 提取表达式中的关键词
|
||||||
// TODO 支持嵌套,类似于 A(abc|bcd)
|
func (this *Regexp) ParseKeywords(exp string) (keywords []string) {
|
||||||
// TODO 支持 (?:xxx)
|
|
||||||
// TODO 支持 (abc)(bcd)(efg)
|
|
||||||
func (this *Regexp) ParseKeywords(exp string) []string {
|
|
||||||
var keywords = []string{}
|
|
||||||
if len(exp) == 0 {
|
if len(exp) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var runes = []rune(exp)
|
reg, err := syntax.Parse(exp, syntax.Perl)
|
||||||
|
if err != nil {
|
||||||
// (a|b|c)
|
return nil
|
||||||
reg, err := regexp.Compile(exp)
|
|
||||||
if err == nil {
|
|
||||||
var countSub = reg.NumSubexp()
|
|
||||||
if countSub == 1 {
|
|
||||||
beginIndex := this.indexOfSymbol(runes, '(')
|
|
||||||
if beginIndex >= 0 {
|
|
||||||
runes = runes[beginIndex+1:]
|
|
||||||
symbolIndex := this.indexOfSymbol(runes, ')')
|
|
||||||
if symbolIndex > 0 && this.isPlain(runes[symbolIndex+1:]) {
|
|
||||||
runes = runes[:symbolIndex]
|
|
||||||
if len(runes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastIndex = 0
|
if len(reg.Sub) == 0 {
|
||||||
for index, r := range runes {
|
var keywordRunes = this.parseKeyword(reg.String())
|
||||||
if r == '|' {
|
if len(keywordRunes) > 0 {
|
||||||
if index > 0 && runes[index-1] != '\\' {
|
keywords = append(keywords, string(keywordRunes))
|
||||||
var ks = this.parseKeyword(runes[lastIndex:index])
|
}
|
||||||
if len(ks) > 0 {
|
return
|
||||||
keywords = append(keywords, string(ks))
|
}
|
||||||
|
if len(reg.Sub) == 1 {
|
||||||
|
if reg.Op == syntax.OpStar || reg.Op == syntax.OpQuest || reg.Op == syntax.OpRepeat {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return this.ParseKeywords(reg.Sub[0].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch reg.Op {
|
||||||
|
case syntax.OpConcat:
|
||||||
|
var prevKeywords = []string{}
|
||||||
|
var isStarted bool
|
||||||
|
for _, sub := range reg.Sub {
|
||||||
|
if sub.String() == `\b` {
|
||||||
|
if isStarted {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sub.Op != syntax.OpLiteral && sub.Op != syntax.OpCapture && sub.Op != syntax.OpAlternate {
|
||||||
|
if isStarted {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var subKeywords = this.ParseKeywords(sub.String())
|
||||||
|
if len(subKeywords) > 0 {
|
||||||
|
if !isStarted {
|
||||||
|
prevKeywords = subKeywords
|
||||||
|
isStarted = true
|
||||||
} else {
|
} else {
|
||||||
return nil
|
for _, prevKeyword := range prevKeywords {
|
||||||
|
for _, subKeyword := range subKeywords {
|
||||||
|
keywords = append(keywords, prevKeyword+subKeyword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevKeywords = keywords
|
||||||
}
|
}
|
||||||
lastIndex = index + 1
|
} else {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if len(prevKeywords) > 0 && len(keywords) == 0 {
|
||||||
if lastIndex == 0 {
|
keywords = prevKeywords
|
||||||
var ks = this.parseKeyword(runes)
|
|
||||||
if len(ks) > 0 {
|
|
||||||
keywords = append(keywords, string(ks))
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
} else if lastIndex > 0 {
|
case syntax.OpAlternate:
|
||||||
var ks = this.parseKeyword(runes[lastIndex:])
|
for _, sub := range reg.Sub {
|
||||||
if len(ks) > 0 {
|
var subKeywords = this.ParseKeywords(sub.String())
|
||||||
keywords = append(keywords, string(ks))
|
if len(subKeywords) == 0 {
|
||||||
} else {
|
keywords = nil
|
||||||
return nil
|
return
|
||||||
|
}
|
||||||
|
keywords = append(keywords, subKeywords...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keywords
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Regexp) parseKeyword(keyword []rune) (result []rune) {
|
func (this *Regexp) parseKeyword(subExp string) (result []rune) {
|
||||||
if len(keyword) == 0 {
|
if len(subExp) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove first \b
|
// 去除开始和结尾的()
|
||||||
for index, r := range keyword {
|
if subExp[0] == '(' && subExp[len(subExp)-1] == ')' {
|
||||||
if r == '\b' {
|
subExp = subExp[1 : len(subExp)-1]
|
||||||
keyword = keyword[index+1:]
|
if len(subExp) == 0 {
|
||||||
break
|
return
|
||||||
} else if r != '\t' && r != '\r' && r != '\n' && r != ' ' {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(keyword) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, r := range keyword {
|
var runes = []rune(subExp)
|
||||||
if index == 0 && r == '^' {
|
|
||||||
continue
|
for index, r := range runes {
|
||||||
}
|
|
||||||
if r == '(' || r == ')' {
|
|
||||||
if index == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if keyword[index-1] != '\\' {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r == '[' || r == '{' || r == '.' || r == '+' || r == '$' {
|
if r == '[' || r == '{' || r == '.' || r == '+' || r == '$' {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
if keyword[index-1] != '\\' {
|
if runes[index-1] != '\\' {
|
||||||
if r == '{' && (braceZero.MatchString(string(keyword[index:])) || braceZero2.MatchString(string(keyword[index:]))) { // r {0, ...}
|
if r == '{' && (braceZeroReg.MatchString(subExp[index:])) || braceZeroReg2.MatchString(subExp[index:]) { // r {0, ...}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return result[:len(result)-1]
|
return result[:len(result)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,36 +235,40 @@ func (this *Regexp) parseKeyword(keyword []rune) (result []rune) {
|
|||||||
}
|
}
|
||||||
if r == '?' || r == '*' {
|
if r == '?' || r == '*' {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
return nil
|
return
|
||||||
|
}
|
||||||
|
if runes[index-1] != '\\' {
|
||||||
|
if len(result) > 0 {
|
||||||
|
return result[:len(result)-1]
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return result[:len(result)-1]
|
|
||||||
}
|
|
||||||
if r == '\\' || r == '\b' {
|
|
||||||
// TODO 将来更精细的处理 \d, \s, \$等
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (r == 'n' || r == 't' || r == 'a' || r == 'f' || r == 'r' || r == 'v' || r == 'x') && index > 0 && runes[index-1] == '\\' {
|
||||||
|
switch r {
|
||||||
|
case 'n':
|
||||||
|
r = '\n'
|
||||||
|
case 't':
|
||||||
|
r = '\t'
|
||||||
|
case 'f':
|
||||||
|
r = '\f'
|
||||||
|
case 'r':
|
||||||
|
r = '\r'
|
||||||
|
case 'v':
|
||||||
|
r = '\v'
|
||||||
|
case 'a':
|
||||||
|
r = '\a'
|
||||||
|
case 'x':
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r == '\\' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
result = append(result, r)
|
result = append(result, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找符号位置
|
|
||||||
func (this *Regexp) indexOfSymbol(runes []rune, symbol rune) int {
|
|
||||||
for index, c := range runes {
|
|
||||||
if c == symbol && (index == 0 || runes[index-1] != '\\') {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否可视为为普通字符
|
|
||||||
func (this *Regexp) isPlain(runes []rune) bool {
|
|
||||||
for _, r := range []rune{'|', '(', ')'} {
|
|
||||||
if this.indexOfSymbol(runes, r) >= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeNode/internal/re"
|
"github.com/TeaOSLab/EdgeNode/internal/re"
|
||||||
"github.com/iwind/TeaGo/assert"
|
"github.com/iwind/TeaGo/assert"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,12 +28,14 @@ func TestRegexp_MatchString(t *testing.T) {
|
|||||||
var r = re.MustCompile("abc")
|
var r = re.MustCompile("abc")
|
||||||
a.IsTrue(r.MatchString("abc"))
|
a.IsTrue(r.MatchString("abc"))
|
||||||
a.IsFalse(r.MatchString("ab"))
|
a.IsFalse(r.MatchString("ab"))
|
||||||
|
a.IsFalse(r.MatchString("ABC"))
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var r = re.MustCompile("(?i)abc|def|ghi")
|
var r = re.MustCompile("(?i)abc|def|ghi")
|
||||||
a.IsTrue(r.MatchString("DEF"))
|
a.IsTrue(r.MatchString("DEF"))
|
||||||
a.IsFalse(r.MatchString("ab"))
|
a.IsFalse(r.MatchString("ab"))
|
||||||
|
a.IsTrue(r.MatchString("ABC"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,39 +49,50 @@ func TestRegexp_Sub(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRegexp_ParseKeywords(t *testing.T) {
|
func TestRegexp_ParseKeywords(t *testing.T) {
|
||||||
|
var r = re.MustCompile("")
|
||||||
|
|
||||||
|
{
|
||||||
|
var keywords = r.ParseKeywords(`\n\t\n\f\r\v\x123`)
|
||||||
|
t.Log(keywords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexp_ParseKeywords2(t *testing.T) {
|
||||||
var a = assert.NewAssertion(t)
|
var a = assert.NewAssertion(t)
|
||||||
|
|
||||||
var r = re.MustCompile("")
|
var r = re.MustCompile("")
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)def"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)def"), []string{"abcdef"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)|(?:def)"), []string{}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)|(?:def)"), []string{"abc", "def"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)|def"), []string{}))
|
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc)"), []string{"abc"}))
|
||||||
|
a.IsTrue(testCompareStrings(r.ParseKeywords("(abc|def|ghi)"), []string{"abc", "def", "ghi"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("(?i:abc)"), []string{}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("(?i:abc)"), []string{}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("\babc"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`\babc`), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords(" \babc"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(` \babc`), []string{" "}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("\babc\b"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`\babc\b`), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("\b(abc)"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`\b(abc)`), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("abc"), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc|efg|hij"), []string{"abc", "efg", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("abc|efg|hij"), []string{"abc", "efg", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg|hij`), []string{"abc|efg", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg*|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg*|hij`), []string{"abc|ef", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg?|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg?|hij`), []string{"abc|ef", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg+|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg+|hij`), []string{"abc|ef", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg{2,10}|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg{2,10}|hij`), []string{"abc|ef", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg{0,10}|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg{0,10}|hij`), []string{"abc|ef", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\|efg.+|hij"), []string{"abc", "hij"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc\|efg.+|hij`), []string{"abc|efg", "hij"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("A(abc|bcd)"), []string{"abc", "bcd"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("A(abc|bcd)"), []string{"Aabc", "Abcd"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("^abc"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("^abc"), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc$"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("abc$"), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\$"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`abc$`), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\d"), []string{"abc"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("abc\\d"), []string{"abc"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("abc{0,4}"), []string{"ab"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("abc{0,4}"), []string{"ab"}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("{0,4}"), []string{}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("{0,4}"), []string{}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("{1,4}"), []string{}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("{1,4}"), []string{}))
|
||||||
a.IsTrue(testCompareStrings(r.ParseKeywords("中文|北京|上海|golang"), []string{"中文", "北京", "上海", "golang"}))
|
a.IsTrue(testCompareStrings(r.ParseKeywords("中文|北京|上海|golang"), []string{"中文", "北京", "上海", "golang"}))
|
||||||
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick)\s*=`), strings.Split("onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick", "|")))
|
||||||
|
a.IsTrue(testCompareStrings(r.ParseKeywords(`/\*(!|\x00)`), []string{"/*"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegexp_ParseKeywords2(t *testing.T) {
|
func TestRegexp_ParseKeywords3(t *testing.T) {
|
||||||
var r = re.MustCompile("")
|
var r = re.MustCompile("")
|
||||||
|
|
||||||
var policy = firewallconfigs.HTTPFirewallTemplate()
|
var policy = firewallconfigs.HTTPFirewallTemplate()
|
||||||
@@ -94,14 +108,23 @@ func TestRegexp_ParseKeywords2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRegexp_MatchString(b *testing.B) {
|
func BenchmarkRegexp_MatchString(b *testing.B) {
|
||||||
var r = re.MustCompile("(?i)abc|def|ghi")
|
var r = re.MustCompile("(?i)(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick|onkeydown|onkeyup|onkeypress)(\\s|%09|%0A|(\\+|%20))*(=|%3D)")
|
||||||
|
//b.Log("keywords:", r.Keywords())
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
r.MatchString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
|
r.MatchString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRegexp_MatchString2(b *testing.B) {
|
func BenchmarkRegexp_MatchString2(b *testing.B) {
|
||||||
var r = regexp.MustCompile("(?i)abc|def|ghi")
|
var r = regexp.MustCompile("(?i)(onmouseover|onmousemove|onmousedown|onmouseup|onerror|onload|onclick|ondblclick|onkeydown|onkeyup|onkeypress)(\\s|%09|%0A|(\\+|%20))*(=|%3D)")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
r.MatchString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexp_MatchString_CaseSensitive(b *testing.B) {
|
||||||
|
var r = re.MustCompile("(abc|def|ghi)")
|
||||||
|
b.Log("keywords:", r.Keywords())
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
r.MatchString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
|
r.MatchString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ func TestNewRuneTree(t *testing.T) {
|
|||||||
a.IsTrue(tree.Lookup("iwind.liu@gmail.com", true))
|
a.IsTrue(tree.Lookup("iwind.liu@gmail.com", true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewRuneTree2(t *testing.T) {
|
||||||
|
var tree = re.NewRuneTree([]string{"abc", "abd", "def", "GHI", "中国", "@"})
|
||||||
|
tree.Lookup("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", true)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRuneMap_Lookup(b *testing.B) {
|
func BenchmarkRuneMap_Lookup(b *testing.B) {
|
||||||
var tree = re.NewRuneTree([]string{"abc", "abd", "def", "ghi", "中国"})
|
var tree = re.NewRuneTree([]string{"abc", "abd", "def", "ghi", "中国"})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
@@ -247,6 +247,12 @@ Loop:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 正在退出时不上报错误
|
||||||
|
if teaconst.IsQuiting {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
_, err = rpcClient.NodeLogRPC().CreateNodeLogs(rpcClient.Context(), &pb.CreateNodeLogsRequest{NodeLogs: logList})
|
_, err = rpcClient.NodeLogRPC().CreateNodeLogs(rpcClient.Context(), &pb.CreateNodeLogsRequest{NodeLogs: logList})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/connectivity"
|
"google.golang.org/grpc/connectivity"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/encoding/gzip"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -137,6 +139,10 @@ func (this *RPCClient) FirewallService() pb.FirewallServiceClient {
|
|||||||
return pb.NewFirewallServiceClient(this.pickConn())
|
return pb.NewFirewallServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) SSLCertService() pb.SSLCertServiceClient {
|
||||||
|
return pb.NewSSLCertServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
// Context 节点上下文信息
|
// Context 节点上下文信息
|
||||||
func (this *RPCClient) Context() context.Context {
|
func (this *RPCClient) Context() context.Context {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -214,12 +220,14 @@ func (this *RPCClient) init() error {
|
|||||||
return errors.New("parse endpoint failed: " + err.Error())
|
return errors.New("parse endpoint failed: " + err.Error())
|
||||||
}
|
}
|
||||||
var conn *grpc.ClientConn
|
var conn *grpc.ClientConn
|
||||||
|
var callOptions = grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024),
|
||||||
|
grpc.UseCompressor(gzip.Name))
|
||||||
if u.Scheme == "http" {
|
if u.Scheme == "http" {
|
||||||
conn, err = grpc.Dial(u.Host, grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
|
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
} else if u.Scheme == "https" {
|
} else if u.Scheme == "https" {
|
||||||
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
})), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
|
})), callOptions)
|
||||||
} else {
|
} else {
|
||||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,12 +74,12 @@ func (this *Cache) Write(key string, value interface{}, expiredAt int64) (ok boo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTimestamp := time.Now().Unix()
|
var currentTimestamp = utils.UnixTime()
|
||||||
if expiredAt <= currentTimestamp {
|
if expiredAt <= currentTimestamp {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxExpiredAt := currentTimestamp + 30*86400
|
var maxExpiredAt = currentTimestamp + 30*86400
|
||||||
if expiredAt > maxExpiredAt {
|
if expiredAt > maxExpiredAt {
|
||||||
expiredAt = maxExpiredAt
|
expiredAt = maxExpiredAt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ttlcache
|
package ttlcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -29,19 +30,26 @@ func TestNewCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCache_Memory(t *testing.T) {
|
func TestCache_Memory(t *testing.T) {
|
||||||
var stat1 = &runtime.MemStats{}
|
testutils.StartMemoryStats(t)
|
||||||
runtime.ReadMemStats(stat1)
|
|
||||||
|
|
||||||
cache := NewCache()
|
var cache = NewCache()
|
||||||
for i := 0; i < 10_000_000; i++ {
|
var count = 2_000_000
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
cache.Write("a"+strconv.Itoa(i), 1, time.Now().Unix()+3600)
|
cache.Write("a"+strconv.Itoa(i), 1, time.Now().Unix()+3600)
|
||||||
}
|
}
|
||||||
t.Log("waiting ...")
|
|
||||||
|
|
||||||
var stat2 = &runtime.MemStats{}
|
t.Log(cache.Count())
|
||||||
runtime.ReadMemStats(stat2)
|
|
||||||
|
|
||||||
t.Log((stat2.HeapInuse-stat1.HeapInuse)/1024/1024, "MB")
|
time.Sleep(1 * time.Second)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
cache.Delete("a" + strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(cache.Count())
|
||||||
|
|
||||||
|
cache.Count()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCache_Add(b *testing.B) {
|
func BenchmarkCache_Add(b *testing.B) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user