Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd41d88647 | ||
|
|
699cea4382 | ||
|
|
dfb0e60acc | ||
|
|
417e10970a | ||
|
|
6437875979 | ||
|
|
beb251a3cc | ||
|
|
8069f20c77 | ||
|
|
8161270f47 | ||
|
|
be7de223af | ||
|
|
7469f528a8 | ||
|
|
1f88db8829 | ||
|
|
844a5fc321 | ||
|
|
06db07ac4b | ||
|
|
00df3fca84 | ||
|
|
4d679e31bc | ||
|
|
06e27bb469 | ||
|
|
684ba7082b | ||
|
|
8934962de2 | ||
|
|
0cf37f25dc | ||
|
|
d7a6d71fea | ||
|
|
a26f7941d5 | ||
|
|
f5365e5420 | ||
|
|
56d21f867b | ||
|
|
d18a301c61 | ||
|
|
afb937030c | ||
|
|
8faa82c453 | ||
|
|
b17b63aec5 | ||
|
|
c30dbb811f | ||
|
|
a58816361e | ||
|
|
8d37aefd95 | ||
|
|
df7fee966e | ||
|
|
01cfccebbd | ||
|
|
6bd7da5e6e | ||
|
|
dcba9c2f3e | ||
|
|
f38e80e82d | ||
|
|
9bd38094c3 | ||
|
|
7e37fc3b80 | ||
|
|
d775dfeeaa | ||
|
|
0486f86898 | ||
|
|
102157c893 | ||
|
|
dbd92368ae | ||
|
|
9e418e73bf | ||
|
|
5d40eec163 | ||
|
|
1a7a67238d | ||
|
|
6707437bae | ||
|
|
df7859387d | ||
|
|
889c52330d | ||
|
|
0e912b79cd | ||
|
|
12f3916e45 | ||
|
|
7813e2c3d2 | ||
|
|
54eff9bfae | ||
|
|
635cdd4338 | ||
|
|
4c64d3ab0f | ||
|
|
93a5c90fcb | ||
|
|
eb5e863146 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*_plus.go
|
||||
*-plus.sh
|
||||
@@ -7,6 +7,7 @@ function build() {
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
TAG=${3}
|
||||
|
||||
if [ -z $OS ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
@@ -16,6 +17,9 @@ function build() {
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $TAG ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
echo "checking ..."
|
||||
ZIP_PATH=$(which zip)
|
||||
@@ -24,8 +28,8 @@ function build() {
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "building v${VERSION}/${OS}/${ARCH} ..."
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
echo "building v${VERSION}/${OS}/${ARCH}/${TAG} ..."
|
||||
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
|
||||
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
@@ -66,6 +70,10 @@ function build() {
|
||||
CC_PATH="aarch64-linux-musl-gcc"
|
||||
CXX_PATH="aarch64-linux-musl-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "arm" ]; then
|
||||
CC_PATH="arm-linux-musleabi-gcc"
|
||||
CXX_PATH="arm-linux-musleabi-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "mips64" ]; then
|
||||
CC_PATH="mips64-linux-musl-gcc"
|
||||
CXX_PATH="mips64-linux-musl-g++"
|
||||
@@ -76,9 +84,9 @@ function build() {
|
||||
fi
|
||||
fi
|
||||
if [ ! -z $CC_PATH ]; then
|
||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS=${OS} GOARCH=${ARCH} CGO_ENABLED=1 go build -o $DIST/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" $ROOT/../cmd/edge-node/main.go
|
||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS=${OS} GOARCH=${ARCH} CGO_ENABLED=1 go build -tags $TAG -o $DIST/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" $ROOT/../cmd/edge-node/main.go
|
||||
else
|
||||
env GOOS=${OS} GOARCH=${ARCH} CGO_ENABLED=1 go build -o $DIST/bin/${NAME} -ldflags="-s -w" $ROOT/../cmd/edge-node/main.go
|
||||
env GOOS=${OS} GOARCH=${ARCH} CGO_ENABLED=1 go build -tags $TAG -o $DIST/bin/${NAME} -ldflags="-s -w" $ROOT/../cmd/edge-node/main.go
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
@@ -110,4 +118,4 @@ function lookup-version() {
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2
|
||||
build $1 $2 $3
|
||||
|
||||
2
build/data/.gitignore
vendored
2
build/data/.gitignore
vendored
@@ -1 +1 @@
|
||||
index.*
|
||||
*
|
||||
1
build/edge-toa/.gitignore
vendored
1
build/edge-toa/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
edge-toa
|
||||
BIN
build/edge-toa/edge-toa
Executable file
BIN
build/edge-toa/edge-toa
Executable file
Binary file not shown.
@@ -5,15 +5,12 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/apps"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/nodes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -40,25 +37,13 @@ func main() {
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("quit", func() {
|
||||
pidFile := Tea.Root + "/bin/pid"
|
||||
data, err := ioutil.ReadFile(pidFile)
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{Code: "quit"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]quit failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
pid := types.Int(string(data))
|
||||
if pid == 0 {
|
||||
fmt.Println("[ERROR]quit failed: pid=0")
|
||||
return
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if process != nil {
|
||||
_ = process.Signal(syscall.SIGQUIT)
|
||||
}
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("pprof", func() {
|
||||
// TODO 自己指定端口
|
||||
|
||||
6
go.mod
6
go.mod
@@ -9,13 +9,15 @@ require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
||||
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible
|
||||
github.com/mattn/go-sqlite3 v1.14.7
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mssola/user_agent v0.5.2
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible
|
||||
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
||||
|
||||
43
go.sum
43
go.sum
@@ -16,12 +16,18 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 h1:CtSi0QlA2Hy+nOh8JAZoiEBLW5pliAiKJ3l1Iq1472I=
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -33,6 +39,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
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-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
@@ -48,7 +56,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
@@ -57,30 +64,34 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f h1:r2O8PONj/KiuZjJHVHn7KlCePUIjNtgAmvLfgRafQ8o=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060 h1:qdLtK4PDXxk2vMKkTWl5Fl9xqYuRCukzWAgJbLHdfOo=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible h1:1qp9iks+69h7IGLazAplzS9Ca14HAxuD5c0rbFdPGy4=
|
||||
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
|
||||
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo=
|
||||
github.com/mssola/user_agent v0.5.2/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -94,8 +105,6 @@ github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mo
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/shirou/gopsutil v2.20.9+incompatible h1:msXs2frUV+O/JLva9EDLpuJ84PrFsdCTCQex8PUdtkQ=
|
||||
github.com/shirou/gopsutil v2.20.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
|
||||
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
@@ -134,8 +143,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -155,10 +164,9 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
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-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4=
|
||||
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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -167,7 +175,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7s
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -184,15 +191,14 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
@@ -201,7 +207,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
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.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
@@ -213,15 +218,15 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -2,8 +2,11 @@ package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -11,7 +14,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// App命令帮助
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
@@ -20,10 +23,14 @@ type AppCmd struct {
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{}
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
@@ -31,25 +38,25 @@ type CommandHelpOption struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
// 产品
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// 版本
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// 使用方法
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usage = usage
|
||||
return this
|
||||
}
|
||||
|
||||
// 选项
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
@@ -58,13 +65,13 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
return this
|
||||
}
|
||||
|
||||
// 附加内容
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// 打印
|
||||
// Print 打印
|
||||
func (this *AppCmd) Print() {
|
||||
fmt.Println(this.product + " v" + this.version)
|
||||
|
||||
@@ -103,7 +110,7 @@ func (this *AppCmd) Print() {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加指令
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
@@ -111,7 +118,7 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
})
|
||||
}
|
||||
|
||||
// 运行
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
@@ -161,7 +168,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
|
||||
// 版本号
|
||||
func (this *AppCmd) runVersion() {
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH+")")
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
|
||||
}
|
||||
|
||||
// 帮助
|
||||
@@ -171,9 +178,9 @@ func (this *AppCmd) runHelp() {
|
||||
|
||||
// 启动
|
||||
func (this *AppCmd) runStart() {
|
||||
proc := this.checkPid()
|
||||
if proc != nil {
|
||||
fmt.Println(this.product+" already started, pid:", proc.Pid)
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -189,18 +196,15 @@ func (this *AppCmd) runStart() {
|
||||
|
||||
// 停止
|
||||
func (this *AppCmd) runStop() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
// 停止进程
|
||||
_ = proc.Kill()
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
// 在Windows上经常不能及时释放资源
|
||||
_ = DeletePid(Tea.Root + "/bin/pid")
|
||||
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// 重启
|
||||
@@ -212,15 +216,24 @@ func (this *AppCmd) runRestart() {
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
proc := this.checkPid()
|
||||
if proc == nil {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
} else {
|
||||
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 检查PID
|
||||
func (this *AppCmd) checkPid() *os.Process {
|
||||
return CheckPid(Tea.Root + "/bin/pid")
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// lock file
|
||||
func LockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
|
||||
func UnlockFile(fp *os.File) error {
|
||||
return errors.New("not implemented on windows")
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var pidFileList = []*os.File{}
|
||||
|
||||
// 检查Pid
|
||||
func CheckPid(path string) *os.Process {
|
||||
// windows上打开的文件是不能删除的
|
||||
if runtime.GOOS == "windows" {
|
||||
if os.Remove(path) == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
// 是否能取得Lock
|
||||
err = LockFile(file)
|
||||
if err == nil {
|
||||
_ = UnlockFile(file)
|
||||
return nil
|
||||
}
|
||||
|
||||
pidBytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
pid := types.Int(string(pidBytes))
|
||||
|
||||
if pid <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
proc, _ := os.FindProcess(pid)
|
||||
return proc
|
||||
}
|
||||
|
||||
// 写入Pid
|
||||
func WritePid() error {
|
||||
path := Tea.Root + "/bin/pid"
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.On(events.EventQuit, func() {
|
||||
_ = fp.Close()
|
||||
})
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 写入Ppid
|
||||
func WritePpid(path string) error {
|
||||
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err = LockFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pidFileList = append(pidFileList, fp) // hold the file pointers
|
||||
|
||||
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除Pid
|
||||
func DeletePid(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fp := range pidFileList {
|
||||
_ = UnlockFile(fp)
|
||||
_ = fp.Close()
|
||||
}
|
||||
return os.Remove(path)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ package caches
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@@ -35,10 +36,15 @@ type FileList struct {
|
||||
itemsTableName string
|
||||
|
||||
isClosed bool
|
||||
|
||||
memoryCache *ttlcache.Cache
|
||||
}
|
||||
|
||||
func NewFileList(dir string) ListInterface {
|
||||
return &FileList{dir: dir}
|
||||
return &FileList{
|
||||
dir: dir,
|
||||
memoryCache: ttlcache.NewCache(),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FileList) Init() error {
|
||||
@@ -54,7 +60,12 @@ func (this *FileList) Init() error {
|
||||
|
||||
this.itemsTableName = "cacheItems_v2"
|
||||
|
||||
db, err := sql.Open("sqlite3", "file:"+this.dir+"/index.db?cache=shared&mode=rwc&_journal_mode=WAL")
|
||||
var dir = this.dir
|
||||
if dir == "/" {
|
||||
// 防止sqlite提示authority错误
|
||||
dir = ""
|
||||
}
|
||||
db, err := sql.Open("sqlite3", "file:"+dir+"/index.db?cache=shared&mode=rwc&_journal_mode=WAL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -95,7 +106,7 @@ func (this *FileList) Init() error {
|
||||
this.total = total
|
||||
|
||||
// 常用语句
|
||||
this.existsByHashStmt, err = this.db.Prepare(`SELECT "bodySize" FROM "` + this.itemsTableName + `" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||
this.existsByHashStmt, err = this.db.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -115,7 +126,7 @@ func (this *FileList) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.statStmt, err = this.db.Prepare(`SELECT COUNT(*), IFNULL(SUM(headerSize+bodySize+metaSize), 0), IFNULL(SUM(headerSize+bodySize), 0) FROM "` + this.itemsTableName + `" WHERE expiredAt>?`)
|
||||
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
|
||||
}
|
||||
@@ -161,6 +172,11 @@ func (this *FileList) Exist(hash string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
item := this.memoryCache.Read(hash)
|
||||
if item != nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
rows, err := this.existsByHashStmt.Query(hash, time.Now().Unix())
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -169,6 +185,12 @@ func (this *FileList) Exist(hash string) (bool, error) {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
if rows.Next() {
|
||||
var expiredAt int64
|
||||
err = rows.Scan(&expiredAt)
|
||||
if err != nil {
|
||||
return true, nil
|
||||
}
|
||||
this.memoryCache.Write(hash, 1, expiredAt)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
@@ -184,6 +206,10 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
this.memoryCache.Clean()
|
||||
}()
|
||||
|
||||
var count = int64(10000)
|
||||
for {
|
||||
result, err := this.db.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0 WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+strconv.FormatInt(count, 10)+`)`, utils.UnixTime(), prefix)
|
||||
@@ -205,6 +231,9 @@ func (this *FileList) Remove(hash string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 从缓存中删除
|
||||
this.memoryCache.Delete(hash)
|
||||
|
||||
row := this.selectByHashStmt.QueryRow(hash)
|
||||
if row.Err() != nil {
|
||||
return row.Err()
|
||||
@@ -283,6 +312,8 @@ func (this *FileList) CleanAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.memoryCache.Clean()
|
||||
|
||||
_, err := this.deleteAllStmt.Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -297,7 +328,7 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
}
|
||||
|
||||
// 这里不设置过期时间、不使用 check 函数,目的是让查询更快速一些
|
||||
row := this.statStmt.QueryRow(time.Now().Unix())
|
||||
row := this.statStmt.QueryRow()
|
||||
if row.Err() != nil {
|
||||
return nil, row.Err()
|
||||
}
|
||||
@@ -329,6 +360,8 @@ func (this *FileList) OnRemove(f func(item *Item)) {
|
||||
func (this *FileList) Close() error {
|
||||
this.isClosed = true
|
||||
|
||||
this.memoryCache.Destroy()
|
||||
|
||||
if this.db != nil {
|
||||
_ = this.existsByHashStmt.Close()
|
||||
_ = this.insertStmt.Close()
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestFileList_Add_Many(t *testing.T) {
|
||||
}
|
||||
before := time.Now()
|
||||
for i := 0; i < 2000_0000; i++ {
|
||||
u := "http://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||
_ = list.Add(stringutil.Md5(u), &Item{
|
||||
Key: u,
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
@@ -162,3 +163,25 @@ func (this *Manager) TotalMemorySize() int64 {
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// FindAllCachePaths 所有缓存路径
|
||||
func (this *Manager) FindAllCachePaths() []string {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var result = []string{}
|
||||
for _, policy := range this.policyMap {
|
||||
if policy.Type == serverconfigs.CachePolicyStorageFile {
|
||||
if policy.Options != nil {
|
||||
dir, ok := policy.Options["dir"]
|
||||
if ok {
|
||||
var dirString = types.String(dir)
|
||||
if len(dirString) > 0 {
|
||||
result = append(result, dirString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -34,13 +34,7 @@ func (this *FileReader) Init() error {
|
||||
}
|
||||
}()
|
||||
|
||||
// 读取状态
|
||||
_, err := this.fp.Seek(SizeExpiresAt, io.SeekStart)
|
||||
if err != nil {
|
||||
_ = this.discard()
|
||||
return err
|
||||
}
|
||||
buf := make([]byte, 3)
|
||||
var buf = make([]byte, SizeMeta)
|
||||
ok, err := this.readToBuff(this.fp, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -48,37 +42,18 @@ func (this *FileReader) Init() error {
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
status := types.Int(string(buf))
|
||||
|
||||
status := types.Int(string(buf[SizeExpiresAt : SizeExpiresAt+SizeStatus]))
|
||||
if status < 100 || status > 999 {
|
||||
return errors.New("invalid status")
|
||||
}
|
||||
this.status = status
|
||||
|
||||
// URL
|
||||
_, err = this.fp.Seek(SizeExpiresAt+SizeStatus, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes4 := make([]byte, 4)
|
||||
ok, err = this.readToBuff(this.fp, bytes4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
urlLength := binary.BigEndian.Uint32(bytes4)
|
||||
urlLength := binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
|
||||
|
||||
// header
|
||||
ok, err = this.readToBuff(this.fp, bytes4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
headerSize := int(binary.BigEndian.Uint32(bytes4))
|
||||
headerSize := int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
|
||||
if headerSize == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -86,15 +61,7 @@ func (this *FileReader) Init() error {
|
||||
this.headerOffset = int64(SizeMeta) + int64(urlLength)
|
||||
|
||||
// body
|
||||
bytes8 := make([]byte, 8)
|
||||
ok, err = this.readToBuff(this.fp, bytes8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
bodySize := int(binary.BigEndian.Uint64(bytes8))
|
||||
bodySize := int(binary.BigEndian.Uint64(buf[SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength+SizeBodyLength]))
|
||||
if bodySize == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ func TestFileReader(t *testing.T) {
|
||||
t.Log("body:", string(buf[:n]))
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileReader_Range(t *testing.T) {
|
||||
|
||||
8
internal/const/build.go
Normal file
8
internal/const/build.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
// +build community
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = true
|
||||
const BuildPlus = false
|
||||
const Tag = "community"
|
||||
8
internal/const/build_plus.go
Normal file
8
internal/const/build_plus.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
// +build plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = false
|
||||
const BuildPlus = true
|
||||
const Tag = "plus"
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.2.3"
|
||||
Version = "0.3.0"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// IP名单
|
||||
// IPList IP名单
|
||||
type IPList struct {
|
||||
itemsMap map[int64]*IPItem // id => item
|
||||
ipMap map[uint64][]int64 // ip => itemIds
|
||||
@@ -96,7 +96,7 @@ func (this *IPList) Delete(itemId int64) {
|
||||
this.isAll = len(this.ipMap[0]) > 0
|
||||
}
|
||||
|
||||
// 判断是否包含某个IP
|
||||
// Contains 判断是否包含某个IP
|
||||
func (this *IPList) Contains(ip uint64) bool {
|
||||
this.locker.RLock()
|
||||
if this.isAll {
|
||||
@@ -109,7 +109,7 @@ func (this *IPList) Contains(ip uint64) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// 是否包含一组IP
|
||||
// ContainsIPStrings 是否包含一组IP
|
||||
func (this *IPList) ContainsIPStrings(ipStrings []string) (found bool, item *IPItem) {
|
||||
if len(ipStrings) == 0 {
|
||||
return
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package iplibrary
|
||||
|
||||
type LibraryInterface interface {
|
||||
// 加载数据库文件
|
||||
// Load 加载数据库文件
|
||||
Load(dbPath string) error
|
||||
|
||||
// 查询IP
|
||||
// Lookup 查询IP
|
||||
// 返回结果有可能为空
|
||||
Lookup(ip string) (*Result, error)
|
||||
|
||||
// 关闭数据库文件
|
||||
// Close 关闭数据库文件
|
||||
Close()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/ip2region"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -27,6 +28,9 @@ func (this *IP2RegionLibrary) Lookup(ip string) (*Result, error) {
|
||||
if strings.Contains(ip, ":") {
|
||||
return nil, nil
|
||||
}
|
||||
if net.ParseIP(ip) == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if this.db == nil {
|
||||
return nil, errors.New("library has not been loaded")
|
||||
|
||||
50
internal/js/console.go
Normal file
50
internal/js/console.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Console struct {
|
||||
}
|
||||
|
||||
func (this *Console) Log(args ...interface{}) {
|
||||
for index, arg := range args {
|
||||
if arg != nil {
|
||||
switch arg.(type) {
|
||||
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string:
|
||||
default:
|
||||
var argType = reflect.TypeOf(arg)
|
||||
|
||||
// 是否有String()方法,如果有直接调用
|
||||
method, ok := argType.MethodByName("String")
|
||||
if ok && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).Kind() == reflect.String {
|
||||
args[index] = method.Func.Call([]reflect.Value{reflect.ValueOf(arg)})[0].String()
|
||||
continue
|
||||
}
|
||||
|
||||
// 转为JSON
|
||||
argJSON, err := this.toJSON(arg)
|
||||
if err != nil {
|
||||
if argType.Kind() == reflect.Func {
|
||||
args[index] = "[function]"
|
||||
} else {
|
||||
args[index] = "[object]"
|
||||
}
|
||||
} else {
|
||||
args[index] = string(argJSON)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
args[index] = "null"
|
||||
}
|
||||
}
|
||||
logs.Println(append([]interface{}{"[js][console]"}, args...)...)
|
||||
}
|
||||
|
||||
func (this *Console) toJSON(o interface{}) ([]byte, error) {
|
||||
return json.Marshal(o)
|
||||
}
|
||||
38
internal/js/console_test.go
Normal file
38
internal/js/console_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConsole_Log(t *testing.T) {
|
||||
{
|
||||
vm := NewVM()
|
||||
_, err := vm.RunString("console.log('Hello', 'world')")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
{
|
||||
vm := NewVM()
|
||||
_, err := vm.RunString("console.log(null, true, false, 10, 10.123)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
{
|
||||
vm := NewVM()
|
||||
_, err := vm.RunString("console.log({ a:1, b:2 })")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
{
|
||||
vm := NewVM()
|
||||
_, err := vm.RunString("console.log(console.log)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
internal/js/http.go
Normal file
36
internal/js/http.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
type HTTP struct {
|
||||
r RequestInterface
|
||||
|
||||
req *Request
|
||||
resp *Response
|
||||
|
||||
onRequest func(req *Request, resp *Response)
|
||||
}
|
||||
|
||||
func NewHTTP(r RequestInterface) *HTTP {
|
||||
return &HTTP{
|
||||
req: NewRequest(r),
|
||||
resp: NewResponse(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *HTTP) OnRequest(callback func(req *Request, resp *Response)) {
|
||||
// TODO 考虑是否支持多个callback
|
||||
this.onRequest = callback
|
||||
}
|
||||
|
||||
func (this *HTTP) OnData(callback func(req *Request, resp *Response)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (this *HTTP) OnResponse(callback func(req *Request, resp *Response)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (this *HTTP) TriggerRequest() {
|
||||
this.onRequest(this.req, this.resp)
|
||||
}
|
||||
82
internal/js/request.go
Normal file
82
internal/js/request.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
r RequestInterface
|
||||
}
|
||||
|
||||
func NewRequest(r RequestInterface) *Request {
|
||||
return &Request{
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Request) Proto() string {
|
||||
return this.r.JSRequest().Proto
|
||||
}
|
||||
|
||||
func (this *Request) Method() string {
|
||||
return this.r.JSRequest().Method
|
||||
}
|
||||
|
||||
func (this *Request) Header() map[string][]string {
|
||||
return this.r.JSRequest().Header
|
||||
}
|
||||
|
||||
func (this *Request) AddHeader(name string, value string) {
|
||||
this.r.JSRequest().Header[name] = append(this.r.JSRequest().Header[name], value)
|
||||
}
|
||||
|
||||
func (this *Request) SetHeader(name string, value string) {
|
||||
this.r.JSRequest().Header[name] = []string{value}
|
||||
}
|
||||
|
||||
func (this *Request) RemoteAddr() string {
|
||||
var remoteAddr = this.r.JSRequest().RemoteAddr
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err == nil {
|
||||
return host
|
||||
}
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
func (this *Request) Url() *URL {
|
||||
return NewURL(this.r.JSRequest().URL)
|
||||
}
|
||||
|
||||
func (this *Request) ContentLength() int64 {
|
||||
return this.r.JSRequest().ContentLength
|
||||
}
|
||||
|
||||
func (this *Request) Body() []byte {
|
||||
var bodyReader = this.r.JSRequest().Body
|
||||
if bodyReader == nil {
|
||||
return []byte{}
|
||||
}
|
||||
data, err := ioutil.ReadAll(bodyReader)
|
||||
if err != nil {
|
||||
this.r.JSLog("read body failed: " + err.Error())
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (this *Request) CopyBody() []byte {
|
||||
var bodyReader = this.r.JSRequest().Body
|
||||
if bodyReader == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(bodyReader)
|
||||
if err != nil {
|
||||
this.r.JSLog("read body failed: " + err.Error())
|
||||
}
|
||||
this.r.JSRequest().Body = ioutil.NopCloser(bytes.NewReader(data))
|
||||
return data
|
||||
}
|
||||
19
internal/js/request_interface.go
Normal file
19
internal/js/request_interface.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import "net/http"
|
||||
|
||||
type RequestInterface interface {
|
||||
// JSRequest 请求
|
||||
JSRequest() *http.Request
|
||||
|
||||
// JSWriter 响应
|
||||
JSWriter() http.ResponseWriter
|
||||
|
||||
// JSStop 中止请求
|
||||
JSStop()
|
||||
|
||||
// JSLog 打印日志
|
||||
JSLog(msg ...interface{})
|
||||
}
|
||||
124
internal/js/request_test.go
Normal file
124
internal/js/request_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/js"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
rawRequest *http.Request
|
||||
rawResponse *testResponse
|
||||
}
|
||||
|
||||
func (this *testRequest) JSRequest() *http.Request {
|
||||
if this.rawRequest != nil {
|
||||
return this.rawRequest
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodGet, "https://iwind:123456@goedge.cn/docs?name=Libai&age=20", nil)
|
||||
req.Header.Set("Server", "edgejs/1.0")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader([]byte("123456")))
|
||||
this.rawRequest = req
|
||||
return req
|
||||
}
|
||||
|
||||
func (this *testRequest) JSWriter() http.ResponseWriter {
|
||||
if this.rawResponse != nil {
|
||||
return this.rawResponse
|
||||
}
|
||||
this.rawResponse = &testResponse{}
|
||||
return this.rawResponse
|
||||
}
|
||||
|
||||
func (this *testRequest) JSStop() {
|
||||
|
||||
}
|
||||
|
||||
func (this *testRequest) JSLog(msg ...interface{}) {
|
||||
logs.Println(msg...)
|
||||
}
|
||||
|
||||
type testResponse struct {
|
||||
statusCode int
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (this *testResponse) Header() http.Header {
|
||||
if this.header == nil {
|
||||
this.header = http.Header{}
|
||||
}
|
||||
return this.header
|
||||
}
|
||||
|
||||
func (this *testResponse) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (this *testResponse) WriteHeader(statusCode int) {
|
||||
this.statusCode = statusCode
|
||||
}
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
vm := js.NewVM()
|
||||
vm.SetRequest(&testRequest{})
|
||||
|
||||
// 事件监听
|
||||
_, err := vm.RunString(`
|
||||
http.onRequest(function (req, resp) {
|
||||
console.log(req.proto())
|
||||
|
||||
let url = req.url()
|
||||
console.log(url, "port:", url.port(), "args:", url.args())
|
||||
console.log("username:", url.username(), "password:", url.password())
|
||||
console.log("uri:", url.uri(), "path:", url.path())
|
||||
|
||||
req.addHeader("Server", "1.0")
|
||||
|
||||
|
||||
resp.write("this is response")
|
||||
console.log(resp)
|
||||
|
||||
console.log(req.body())
|
||||
})
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
_, err = vm.RunString(`http.triggerRequest()`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequest_Header(t *testing.T) {
|
||||
var req = js.NewRequest(&testRequest{})
|
||||
logs.PrintAsJSON(req.Header(), t)
|
||||
|
||||
req.AddHeader("Content-Length", "10")
|
||||
req.AddHeader("Vary", "1.0")
|
||||
req.AddHeader("Vary", "2.0")
|
||||
logs.PrintAsJSON(req.Header(), t)
|
||||
|
||||
req.SetHeader("Vary", "3.0")
|
||||
logs.PrintAsJSON(req.Header(), t)
|
||||
}
|
||||
|
||||
func TestRequest_Body(t *testing.T) {
|
||||
var req = js.NewRequest(&testRequest{})
|
||||
t.Log(string(req.Body()))
|
||||
t.Log(string(req.Body()))
|
||||
}
|
||||
|
||||
func TestRequest_CopyBody(t *testing.T) {
|
||||
var req = js.NewRequest(&testRequest{})
|
||||
t.Log(string(req.CopyBody()))
|
||||
t.Log(string(req.CopyBody()))
|
||||
}
|
||||
39
internal/js/response.go
Normal file
39
internal/js/response.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
type Response struct {
|
||||
r RequestInterface
|
||||
}
|
||||
|
||||
func NewResponse(r RequestInterface) *Response {
|
||||
return &Response{
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Response) Write(s string) error {
|
||||
_, err := this.r.JSWriter().Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *Response) Reply(status int) {
|
||||
this.SetStatus(status)
|
||||
this.r.JSStop()
|
||||
}
|
||||
|
||||
func (this *Response) Header() map[string][]string {
|
||||
return this.r.JSWriter().Header()
|
||||
}
|
||||
|
||||
func (this *Response) AddHeader(name string, value string) {
|
||||
this.r.JSWriter().Header()[name] = append(this.r.JSWriter().Header()[name], value)
|
||||
}
|
||||
|
||||
func (this *Response) SetHeader(name string, value string) {
|
||||
this.r.JSWriter().Header()[name] = []string{value}
|
||||
}
|
||||
|
||||
func (this *Response) SetStatus(statusCode int) {
|
||||
this.r.JSWriter().WriteHeader(statusCode)
|
||||
}
|
||||
16
internal/js/response_test.go
Normal file
16
internal/js/response_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/js"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewResponse(t *testing.T) {
|
||||
var resp = js.NewResponse(&testRequest{})
|
||||
resp.AddHeader("Vary", "1.0")
|
||||
resp.AddHeader("Vary", "2.0")
|
||||
resp.SetHeader("Server", "edgejs/1.0")
|
||||
t.Logf("%#v", resp.Header())
|
||||
}
|
||||
90
internal/js/url.go
Normal file
90
internal/js/url.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type URL struct {
|
||||
u *url.URL
|
||||
}
|
||||
|
||||
func NewURL(u *url.URL) *URL {
|
||||
return &URL{
|
||||
u: u,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *URL) JSNew(args []goja.Value) *URL {
|
||||
var urlString = ""
|
||||
if len(args) == 1 {
|
||||
urlString = args[0].String()
|
||||
}
|
||||
u, _ := url.Parse(urlString)
|
||||
if u == nil {
|
||||
u = &url.URL{}
|
||||
}
|
||||
return NewURL(u)
|
||||
}
|
||||
|
||||
func (this *URL) Port() int {
|
||||
return types.Int(this.u.Port())
|
||||
}
|
||||
|
||||
func (this *URL) Args() map[string][]string {
|
||||
return this.u.Query()
|
||||
}
|
||||
|
||||
func (this *URL) Arg(name string) string {
|
||||
return this.u.Query().Get(name)
|
||||
}
|
||||
|
||||
func (this *URL) Username() string {
|
||||
if this.u.User != nil {
|
||||
return this.u.User.Username()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *URL) Password() string {
|
||||
if this.u.User != nil {
|
||||
password, _ := this.u.User.Password()
|
||||
return password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *URL) Uri() string {
|
||||
return this.u.RequestURI()
|
||||
}
|
||||
|
||||
func (this *URL) Path() string {
|
||||
return this.u.Path
|
||||
}
|
||||
|
||||
func (this *URL) Host() string {
|
||||
return this.u.Host
|
||||
}
|
||||
|
||||
func (this *URL) Fragment() string {
|
||||
return this.u.Fragment
|
||||
}
|
||||
|
||||
func (this *URL) Hash() string {
|
||||
if len(this.u.Fragment) > 0 {
|
||||
return "#" + this.u.Fragment
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (this *URL) Scheme() string {
|
||||
return this.u.Scheme
|
||||
}
|
||||
|
||||
func (this *URL) String() string {
|
||||
return this.u.String()
|
||||
}
|
||||
18
internal/js/url_test.go
Normal file
18
internal/js/url_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
raw, err := url.Parse("https://iwind:123456@goedge.cn/docs?name=Libai&age=20#a=b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var u = NewURL(raw)
|
||||
t.Log("host:", u.Host())
|
||||
t.Log("hash:", u.Hash())
|
||||
}
|
||||
153
internal/js/vm.go
Normal file
153
internal/js/vm.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var sharedPrograms []*goja.Program
|
||||
var sharedConsole = &Console{}
|
||||
|
||||
func init() {
|
||||
// compile programs
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
vm *goja.Runtime
|
||||
}
|
||||
|
||||
func NewVM() *VM {
|
||||
vm := goja.New()
|
||||
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
||||
|
||||
// programs
|
||||
for _, program := range sharedPrograms {
|
||||
_, _ = vm.RunProgram(program)
|
||||
}
|
||||
|
||||
v := &VM{vm: vm}
|
||||
v.initVM()
|
||||
return v
|
||||
}
|
||||
|
||||
func (this *VM) Set(name string, obj interface{}) error {
|
||||
return this.vm.Set(name, obj)
|
||||
}
|
||||
|
||||
func (this *VM) AddConstructor(name string, instance interface{}) error {
|
||||
objType := reflect.TypeOf(instance)
|
||||
|
||||
if objType.Kind() != reflect.Ptr {
|
||||
return errors.New("instance should be pointer")
|
||||
}
|
||||
|
||||
// construct
|
||||
newMethod, ok := objType.MethodByName("JSNew")
|
||||
if !ok {
|
||||
return errors.New("can not find 'JSNew()' method in '" + objType.Elem().Name() + "'")
|
||||
}
|
||||
|
||||
var err = this.Set(name, func(call goja.ConstructorCall) *goja.Object {
|
||||
if newMethod.Type.NumIn() != 2 {
|
||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
|
||||
return nil
|
||||
}
|
||||
if newMethod.Type.In(1).String() != "[]goja.Value" {
|
||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// new
|
||||
var results = newMethod.Func.Call([]reflect.Value{reflect.ValueOf(instance), reflect.ValueOf(call.Arguments)})
|
||||
if len(results) == 0 {
|
||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a valid instance"))
|
||||
return nil
|
||||
}
|
||||
var result = results[0]
|
||||
if result.Type() != objType {
|
||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a same instance"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// methods
|
||||
var resultType = result.Type()
|
||||
var numMethod = result.NumMethod()
|
||||
for i := 0; i < numMethod; i++ {
|
||||
var method = resultType.Method(i)
|
||||
var methodName = strings.ToLower(method.Name[:1]) + method.Name[1:]
|
||||
err := call.This.Set(methodName, result.MethodByName(method.Name).Interface())
|
||||
if err != nil {
|
||||
this.throw(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 支持属性
|
||||
var numField = result.Elem().Type().NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
var field = result.Elem().Field(i)
|
||||
if !field.CanInterface() {
|
||||
continue
|
||||
}
|
||||
var fieldType = objType.Elem().Field(i)
|
||||
tag, ok := fieldType.Tag.Lookup("json")
|
||||
if !ok {
|
||||
tag = fieldType.Name
|
||||
tag = strings.ToLower(tag[:1]) + tag[1:]
|
||||
} else {
|
||||
// TODO 校验tag是否符合变量语法
|
||||
}
|
||||
err := call.This.Set(tag, field.Interface())
|
||||
if err != nil {
|
||||
this.throw(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *VM) RunString(str string) (goja.Value, error) {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
// TODO 需要打印trace
|
||||
logs.Println("panic:", e)
|
||||
}
|
||||
}()
|
||||
return this.vm.RunString(str)
|
||||
}
|
||||
|
||||
func (this *VM) SetRequest(req RequestInterface) {
|
||||
{
|
||||
err := this.vm.Set("http", NewHTTP(req))
|
||||
if err != nil {
|
||||
this.throw(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *VM) initVM() {
|
||||
{
|
||||
err := this.vm.Set("console", sharedConsole)
|
||||
if err != nil {
|
||||
this.throw(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *VM) throw(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO
|
||||
logs.Println("js:VM:error: " + err.Error())
|
||||
}
|
||||
158
internal/js/vm_test.go
Normal file
158
internal/js/vm_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewVM(t *testing.T) {
|
||||
before := time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
vm := NewVM()
|
||||
{
|
||||
v, err := vm.RunString("JSON.stringify({\"a\":\"b\"})")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("JSON.stringify():", v)
|
||||
}
|
||||
{
|
||||
v, err := vm.RunString(`JSON.parse('{\"a\":\"b\"}')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("JSON.parse():", v)
|
||||
}
|
||||
{
|
||||
err := vm.AddConstructor("Url", &URL{})
|
||||
if err != nil {
|
||||
t.Fatal("add constructor error:", err)
|
||||
}
|
||||
_, err = vm.RunString(`
|
||||
{
|
||||
let u = new Url("https://goedge.cn/docs?v=1")
|
||||
console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
{
|
||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||
console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url()
|
||||
console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url("a", "b", "c")
|
||||
console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal("add constructor error:" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVM_Program(t *testing.T) {
|
||||
var s = `
|
||||
{
|
||||
let u = new Url("https://goedge.cn/docs?v=1")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
{
|
||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url()
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url("a", "b", "c")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
`
|
||||
program := goja.MustCompile("s", s, true)
|
||||
|
||||
before := time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
vm := NewVM()
|
||||
err := vm.AddConstructor("Url", &URL{})
|
||||
if err != nil {
|
||||
t.Fatal("add constructor error:", err)
|
||||
}
|
||||
//_, err = vm.RunString(s)
|
||||
_, err = vm.vm.RunProgram(program)
|
||||
if err != nil {
|
||||
t.Fatal("add constructor error:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Program(b *testing.B) {
|
||||
var s = `
|
||||
{
|
||||
let u = new Url("https://goedge.cn/docs?v=1")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
{
|
||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url()
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url("a", "b", "c")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
{
|
||||
let u = new Url("https://goedge.cn/docs?v=1")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
{
|
||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url()
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
|
||||
{
|
||||
let u = new Url("a", "b", "c")
|
||||
//console.log("host:", u.host(), u.uri())
|
||||
}
|
||||
`
|
||||
program := goja.MustCompile("s", s, true)
|
||||
|
||||
vm := NewVM()
|
||||
|
||||
err := vm.AddConstructor("Url", &URL{})
|
||||
if err != nil {
|
||||
b.Fatal("add constructor error:", err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
//_, err = vm.RunString(s)
|
||||
_, err = vm.vm.RunProgram(program)
|
||||
if err != nil {
|
||||
b.Fatal("add constructor error:" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
121
internal/metrics/manager.go
Normal file
121
internal/metrics/manager.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var SharedManager = NewManager()
|
||||
|
||||
type Manager struct {
|
||||
tasks map[int64]*Task // itemId => *Task
|
||||
categoryTasks map[string][]*Task // category => []*Task
|
||||
locker sync.RWMutex
|
||||
|
||||
hasHTTPMetrics bool
|
||||
hasTCPMetrics bool
|
||||
hasUDPMetrics bool
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
tasks: map[int64]*Task{},
|
||||
categoryTasks: map[string][]*Task{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var newMap = map[int64]*serverconfigs.MetricItemConfig{}
|
||||
for _, item := range items {
|
||||
newMap[item.Id] = item
|
||||
}
|
||||
|
||||
// 停用以前的 或 修改现在的
|
||||
for itemId, task := range this.tasks {
|
||||
newItem, ok := newMap[itemId]
|
||||
if !ok || !newItem.IsOn { // 停用以前的
|
||||
remotelogs.Println("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"'")
|
||||
err := task.Stop()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
|
||||
}
|
||||
delete(this.tasks, itemId)
|
||||
} else { // 更新已存在的
|
||||
if newItem.Version != task.item.Version {
|
||||
remotelogs.Println("METRIC_MANAGER", "update task '"+strconv.FormatInt(itemId, 10)+"'")
|
||||
task.item = newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动新的
|
||||
for _, newItem := range items {
|
||||
if !newItem.IsOn {
|
||||
continue
|
||||
}
|
||||
_, ok := this.tasks[newItem.Id]
|
||||
if !ok {
|
||||
remotelogs.Println("METRIC_MANAGER", "start task '"+strconv.FormatInt(newItem.Id, 10)+"'")
|
||||
task := NewTask(newItem)
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "initialized task failed: "+err.Error())
|
||||
continue
|
||||
}
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "start task failed: "+err.Error())
|
||||
continue
|
||||
}
|
||||
this.tasks[newItem.Id] = task
|
||||
}
|
||||
}
|
||||
|
||||
// 按分类存放
|
||||
this.hasHTTPMetrics = false
|
||||
this.hasTCPMetrics = false
|
||||
this.hasUDPMetrics = false
|
||||
this.categoryTasks = map[string][]*Task{}
|
||||
for _, task := range this.tasks {
|
||||
tasks := this.categoryTasks[task.item.Category]
|
||||
tasks = append(tasks, task)
|
||||
this.categoryTasks[task.item.Category] = tasks
|
||||
|
||||
switch task.item.Category {
|
||||
case serverconfigs.MetricItemCategoryHTTP:
|
||||
this.hasHTTPMetrics = true
|
||||
case serverconfigs.MetricItemCategoryTCP:
|
||||
this.hasTCPMetrics = true
|
||||
case serverconfigs.MetricItemCategoryUDP:
|
||||
this.hasUDPMetrics = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add 添加数据
|
||||
func (this *Manager) Add(obj MetricInterface) {
|
||||
this.locker.RLock()
|
||||
for _, task := range this.categoryTasks[obj.MetricCategory()] {
|
||||
task.Add(obj)
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
}
|
||||
|
||||
func (this *Manager) HasHTTPMetrics() bool {
|
||||
return this.hasHTTPMetrics
|
||||
}
|
||||
|
||||
func (this *Manager) HasTCPMetrics() bool {
|
||||
return this.hasTCPMetrics
|
||||
}
|
||||
|
||||
func (this *Manager) HasUDPMetrics() bool {
|
||||
return this.hasUDPMetrics
|
||||
}
|
||||
63
internal/metrics/manager_test.go
Normal file
63
internal/metrics/manager_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
var manager = NewManager()
|
||||
{
|
||||
manager.Update([]*serverconfigs.MetricItemConfig{})
|
||||
for _, task := range manager.tasks {
|
||||
t.Log(task.item.Id)
|
||||
}
|
||||
}
|
||||
{
|
||||
t.Log("====")
|
||||
manager.Update([]*serverconfigs.MetricItemConfig{
|
||||
{
|
||||
Id: 1,
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.tasks {
|
||||
t.Log("task:", task.item.Id)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("====")
|
||||
manager.Update([]*serverconfigs.MetricItemConfig{
|
||||
{
|
||||
Id: 1,
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.tasks {
|
||||
t.Log("task:", task.item.Id)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("====")
|
||||
manager.Update([]*serverconfigs.MetricItemConfig{
|
||||
{
|
||||
Id: 1,
|
||||
Version: 1,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.tasks {
|
||||
t.Log("task:", task.item.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
internal/metrics/metric_interface.go
Normal file
17
internal/metrics/metric_interface.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
type MetricInterface interface {
|
||||
// MetricKey 指标对象
|
||||
MetricKey(key string) string
|
||||
|
||||
// MetricValue 指标值
|
||||
MetricValue(value string) (result int64, ok bool)
|
||||
|
||||
// MetricServerId 服务ID
|
||||
MetricServerId() int64
|
||||
|
||||
// MetricCategory 指标分类
|
||||
MetricCategory() string
|
||||
}
|
||||
22
internal/metrics/stat.go
Normal file
22
internal/metrics/stat.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/cespare/xxhash"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Stat struct {
|
||||
ServerId int64
|
||||
Keys []string
|
||||
Hash string
|
||||
Value int64
|
||||
Time string
|
||||
}
|
||||
|
||||
func SumStat(serverId int64, keys []string, time string, version int32, itemId int64) string {
|
||||
keysData, _ := json.Marshal(keys)
|
||||
return strconv.FormatUint(xxhash.Sum64String(strconv.FormatInt(serverId, 10)+"@"+string(keysData)+"@"+time+"@"+strconv.Itoa(int(version))+"@"+strconv.FormatInt(itemId, 10)), 10)
|
||||
}
|
||||
503
internal/metrics/task.go
Normal file
503
internal/metrics/task.go
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const MaxQueueSize = 10240
|
||||
|
||||
// Task 单个指标任务
|
||||
// 数据库存储:
|
||||
// data/
|
||||
// metric.$ID.db
|
||||
// stats
|
||||
// id, keys, value, time, serverId, hash
|
||||
// 原理:
|
||||
// 添加或者有变更时 isUploaded = false
|
||||
// 上传时检查 isUploaded 状态
|
||||
// 只上传每个服务中排序最前面的 N 个数据
|
||||
type Task struct {
|
||||
item *serverconfigs.MetricItemConfig
|
||||
isLoaded bool
|
||||
|
||||
db *sql.DB
|
||||
statTableName string
|
||||
isStopped bool
|
||||
|
||||
cleanTicker *utils.Ticker
|
||||
uploadTicker *utils.Ticker
|
||||
|
||||
cleanVersion int32
|
||||
|
||||
insertStatStmt *sql.Stmt
|
||||
deleteByVersionStmt *sql.Stmt
|
||||
deleteByExpiresTimeStmt *sql.Stmt
|
||||
selectTopStmt *sql.Stmt
|
||||
sumStmt *sql.Stmt
|
||||
|
||||
serverIdMap map[int64]bool // 所有的服务Ids
|
||||
timeMap map[string]bool // time => bool
|
||||
serverIdMapLocker sync.Mutex
|
||||
|
||||
statsMap map[string]*Stat
|
||||
statsLocker sync.Mutex
|
||||
statsTicker *utils.Ticker
|
||||
}
|
||||
|
||||
// NewTask 获取新任务
|
||||
func NewTask(item *serverconfigs.MetricItemConfig) *Task {
|
||||
return &Task{
|
||||
item: item,
|
||||
serverIdMap: map[int64]bool{},
|
||||
timeMap: map[string]bool{},
|
||||
statsMap: map[string]*Stat{},
|
||||
}
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (this *Task) Init() error {
|
||||
this.statTableName = "stats"
|
||||
|
||||
// 检查目录是否存在
|
||||
var dir = Tea.Root + "/data"
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
this.db = db
|
||||
|
||||
//创建统计表
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"keys" varchar(1024),
|
||||
"value" real DEFAULT 0,
|
||||
"time" varchar(32),
|
||||
"serverId" integer DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"isUploaded" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "serverId"
|
||||
ON "` + this.statTableName + `" (
|
||||
"serverId" ASC,
|
||||
"version" ASC
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
||||
ON "` + this.statTableName + `" (
|
||||
"hash" ASC
|
||||
);`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// insert stat stmt
|
||||
this.insertStatStmt, err = db.Prepare(`INSERT INTO "stats" ("serverId", "hash", "keys", "value", "time", "version", "isUploaded") VALUES (?, ?, ?, ?, ?, ?, 0) ON CONFLICT("hash") DO UPDATE SET "value"="value"+?, "isUploaded"=0`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete by version
|
||||
this.deleteByVersionStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "version"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete by expires time
|
||||
this.deleteByExpiresTimeStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "time"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// select topN stmt
|
||||
this.selectTopStmt, err = db.Prepare(`SELECT "id", "hash", "keys", "value", "isUploaded" FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=? ORDER BY "value" DESC LIMIT 20`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sum stmt
|
||||
this.sumStmt, err = db.Prepare(`SELECT COUNT(*), IFNULL(SUM(value), 0) FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 所有的服务IDs
|
||||
err = this.loadServerIdMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.isLoaded = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动任务
|
||||
func (this *Task) Start() error {
|
||||
// 读取数据
|
||||
this.statsTicker = utils.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for this.statsTicker.Next() {
|
||||
this.statsLocker.Lock()
|
||||
var statsMap = this.statsMap
|
||||
this.statsMap = map[string]*Stat{}
|
||||
this.statsLocker.Unlock()
|
||||
|
||||
for _, stat := range statsMap {
|
||||
err := this.InsertStat(stat)
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 清理
|
||||
this.cleanTicker = utils.NewTicker(24 * time.Hour)
|
||||
go func() {
|
||||
for this.cleanTicker.Next() {
|
||||
err := this.CleanExpired()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 上传
|
||||
this.uploadTicker = utils.NewTicker(this.item.UploadDuration())
|
||||
go func() {
|
||||
for this.uploadTicker.Next() {
|
||||
err := this.Upload(1 * time.Second)
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add 添加数据
|
||||
func (this *Task) Add(obj MetricInterface) {
|
||||
if this.isStopped || !this.isLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
var keys = []string{}
|
||||
for _, key := range this.item.Keys {
|
||||
k := obj.MetricKey(key)
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
v, ok := obj.MetricValue(this.item.Value)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var hash = SumStat(obj.MetricServerId(), keys, this.item.CurrentTime(), this.item.Version, this.item.Id)
|
||||
this.statsLocker.Lock()
|
||||
oldStat, ok := this.statsMap[hash]
|
||||
if ok {
|
||||
oldStat.Value += v
|
||||
oldStat.Hash = hash
|
||||
} else {
|
||||
// 防止过载
|
||||
if len(this.statsMap) < MaxQueueSize {
|
||||
this.statsMap[hash] = &Stat{
|
||||
ServerId: obj.MetricServerId(),
|
||||
Keys: keys,
|
||||
Value: v,
|
||||
Time: this.item.CurrentTime(),
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
this.statsLocker.Unlock()
|
||||
}
|
||||
|
||||
// Stop 停止任务
|
||||
func (this *Task) Stop() error {
|
||||
this.isStopped = true
|
||||
|
||||
if this.cleanTicker != nil {
|
||||
this.cleanTicker.Stop()
|
||||
}
|
||||
if this.uploadTicker != nil {
|
||||
this.uploadTicker.Stop()
|
||||
}
|
||||
if this.statsTicker != nil {
|
||||
this.statsTicker.Stop()
|
||||
}
|
||||
|
||||
_ = this.insertStatStmt.Close()
|
||||
_ = this.deleteByVersionStmt.Close()
|
||||
_ = this.deleteByExpiresTimeStmt.Close()
|
||||
_ = this.selectTopStmt.Close()
|
||||
_ = this.sumStmt.Close()
|
||||
|
||||
if this.db != nil {
|
||||
_ = this.db.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertStat 写入数据
|
||||
func (this *Task) InsertStat(stat *Stat) error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
if stat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverIdMap[stat.ServerId] = true
|
||||
this.timeMap[stat.Time] = true
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
keyData, err := json.Marshal(stat.Keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.insertStatStmt.Exec(stat.ServerId, stat.Hash, keyData, stat.Value, stat.Time, this.item.Version, stat.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanExpired 清理数据
|
||||
func (this *Task) CleanExpired() error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清除低版本数据
|
||||
if this.cleanVersion < this.item.Version {
|
||||
_, err := this.deleteByVersionStmt.Exec(this.item.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.cleanVersion = this.item.Version
|
||||
}
|
||||
|
||||
// 清除过期的数据
|
||||
_, err := this.deleteByExpiresTimeStmt.Exec(this.item.LocalExpiresTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload 上传数据
|
||||
func (this *Task) Upload(pauseDuration time.Duration) error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
|
||||
// 服务IDs
|
||||
var serverIds []int64
|
||||
for serverId := range this.serverIdMap {
|
||||
serverIds = append(serverIds, serverId)
|
||||
}
|
||||
this.serverIdMap = map[int64]bool{} // 清空数据
|
||||
|
||||
// 时间
|
||||
var times = []string{}
|
||||
for t := range this.timeMap {
|
||||
times = append(times, t)
|
||||
}
|
||||
this.timeMap = map[string]bool{} // 清空数据
|
||||
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, serverId := range serverIds {
|
||||
for _, currentTime := range times {
|
||||
idStrings, err := func(serverId int64, currentTime string) (ids []string, err error) {
|
||||
rows, err := this.selectTopStmt.Query(serverId, this.item.Version, currentTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var isClosed bool
|
||||
defer func() {
|
||||
if isClosed {
|
||||
return
|
||||
}
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var pbStats []*pb.UploadingMetricStat
|
||||
for rows.Next() {
|
||||
var pbStat = &pb.UploadingMetricStat{}
|
||||
// "id", "hash", "keys", "value", "isUploaded"
|
||||
var isUploaded int
|
||||
var keysData []byte
|
||||
err = rows.Scan(&pbStat.Id, &pbStat.Hash, &keysData, &pbStat.Value, &isUploaded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO 先不判断是否已经上传,需要改造API进行配合
|
||||
/**if isUploaded == 1 {
|
||||
continue
|
||||
}**/
|
||||
if len(keysData) > 0 {
|
||||
err = json.Unmarshal(keysData, &pbStat.Keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pbStats = append(pbStats, pbStat)
|
||||
ids = append(ids, strconv.FormatInt(pbStat.Id, 10))
|
||||
}
|
||||
|
||||
// 提前关闭
|
||||
_ = rows.Close()
|
||||
isClosed = true
|
||||
|
||||
// 上传
|
||||
if len(pbStats) > 0 {
|
||||
// 计算总和
|
||||
count, total, err := this.sum(serverId, currentTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rpcClient.MetricStatRPC().UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
|
||||
MetricStats: pbStats,
|
||||
Time: currentTime,
|
||||
ServerId: serverId,
|
||||
ItemId: this.item.Id,
|
||||
Version: this.item.Version,
|
||||
Count: count,
|
||||
Total: float32(total),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}(serverId, currentTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(idStrings) > 0 {
|
||||
// 设置为已上传
|
||||
_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 休息一下,防止短时间内上传数据过多
|
||||
if pauseDuration > 0 {
|
||||
time.Sleep(pauseDuration)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加载服务ID
|
||||
func (this *Task) loadServerIdMap() error {
|
||||
{
|
||||
rows, err := this.db.Query(`SELECT DISTINCT "serverId" FROM `+this.statTableName+" WHERE version=?", this.item.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var serverId int64
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&serverId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverIdMap[serverId] = true
|
||||
this.serverIdMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
rows, err := this.db.Query(`SELECT DISTINCT "time" FROM `+this.statTableName+" WHERE version=?", this.item.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var timeString string
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.timeMap[timeString] = true
|
||||
this.serverIdMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算数量和综合
|
||||
func (this *Task) sum(serverId int64, time string) (count int64, total float64, err error) {
|
||||
rows, err := this.sumStmt.Query(serverId, this.item.Version, time)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&count, &total)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
210
internal/metrics/task_test.go
Normal file
210
internal/metrics/task_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testObj struct {
|
||||
ip string
|
||||
}
|
||||
|
||||
func (this *testObj) MetricKey(key string) string {
|
||||
return this.ip
|
||||
}
|
||||
|
||||
func (this *testObj) MetricValue(value string) (int64, bool) {
|
||||
return 1, true
|
||||
}
|
||||
|
||||
func (this *testObj) MetricServerId() int64 {
|
||||
return int64(rands.Int(1, 100))
|
||||
}
|
||||
|
||||
func (this *testObj) MetricCategory() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func TestTask_Init(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 0,
|
||||
PeriodUnit: "",
|
||||
Keys: nil,
|
||||
Value: "",
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestTask_Add(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
|
||||
task.Add(&testObj{ip: "127.0.0.2"})
|
||||
time.Sleep(1 * time.Second) // waiting for inserting
|
||||
}
|
||||
|
||||
func TestTask_Add_Many(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
|
||||
for i := 0; i < 4_000_000; i++ {
|
||||
task.Add(&testObj{
|
||||
ip: fmt.Sprintf("%d.%d.%d.%d", rands.Int(0, 255), rands.Int(0, 255), rands.Int(0, 255), rands.Int(0, 255)),
|
||||
})
|
||||
if i%10000 == 0 {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_InsertStat(t *testing.T) {
|
||||
var item = &serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
}
|
||||
var task = metrics.NewTask(item)
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
|
||||
err = task.InsertStat(&metrics.Stat{
|
||||
ServerId: 1,
|
||||
Keys: []string{"127.0.0.1"},
|
||||
Hash: "",
|
||||
Value: 1,
|
||||
Time: item.CurrentTime(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestTask_CleanExpired(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
|
||||
err = task.CleanExpired()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestTask_Upload(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
|
||||
err = task.Upload(0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
@@ -72,7 +72,8 @@ func (this *ValueQueue) Loop() error {
|
||||
CreatedAt: value.CreatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
remotelogs.Error("MONITOR", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
@@ -367,7 +369,28 @@ func (this *APIStream) handlePreheatCache(message *pb.NodeStreamMessage) error {
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(msg.Keys))
|
||||
client := http.Client{} // TODO 可以设置请求超时事件
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second, // TODO 可以设置请求超时时间
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.Dial(network, "127.0.0.1:"+port)
|
||||
},
|
||||
MaxIdleConns: 4096,
|
||||
MaxIdleConnsPerHost: 32,
|
||||
MaxConnsPerHost: 32,
|
||||
IdleConnTimeout: 2 * time.Minute,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSHandshakeTimeout: 0,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
defer client.CloseIdleConnections()
|
||||
errorMessages := []string{}
|
||||
locker := sync.Mutex{}
|
||||
for _, key := range msg.Keys {
|
||||
@@ -381,7 +404,9 @@ func (this *APIStream) handlePreheatCache(message *pb.NodeStreamMessage) error {
|
||||
locker.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 可以在管理界面自定义Header
|
||||
req.Header.Set("X-Cache-Action", "preheat")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36")
|
||||
req.Header.Set("Accept-Encoding", "gzip, deflate, br") // TODO 这里需要记录下缓存是否为gzip的
|
||||
resp, err := client.Do(req)
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
|
||||
var sharedHTTPAccessLogQueue = NewHTTPAccessLogQueue()
|
||||
|
||||
// HTTP访问日志队列
|
||||
// HTTPAccessLogQueue HTTP访问日志队列
|
||||
type HTTPAccessLogQueue struct {
|
||||
queue chan *pb.HTTPAccessLog
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
// NewHTTPAccessLogQueue 获取新对象
|
||||
func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
|
||||
// 队列中最大的值,超出此数量的访问日志会被抛弃
|
||||
// 队列中最大的值,超出此数量的访问日志会被丢弃
|
||||
// TODO 需要可以在界面中设置
|
||||
maxSize := 10000
|
||||
queue := &HTTPAccessLogQueue{
|
||||
@@ -27,7 +27,7 @@ func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
|
||||
return queue
|
||||
}
|
||||
|
||||
// 开始处理访问日志
|
||||
// Start 开始处理访问日志
|
||||
func (this *HTTPAccessLogQueue) Start() {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
for range ticker.C {
|
||||
@@ -38,7 +38,7 @@ func (this *HTTPAccessLogQueue) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加入新访问日志
|
||||
// Push 加入新访问日志
|
||||
func (this *HTTPAccessLogQueue) Push(accessLog *pb.HTTPAccessLog) {
|
||||
select {
|
||||
case this.queue <- accessLog:
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -66,12 +67,16 @@ type HTTPRequest struct {
|
||||
cacheRef *serverconfigs.HTTPCacheRef // 缓存设置
|
||||
cacheKey string // 缓存使用的Key
|
||||
isCached bool // 是否已经被缓存
|
||||
isAttack bool // 是否是攻击请求
|
||||
bodyData []byte // 读取的Body内容
|
||||
|
||||
// WAF相关
|
||||
firewallPolicyId int64
|
||||
firewallRuleGroupId int64
|
||||
firewallRuleSetId int64
|
||||
firewallRuleId int64
|
||||
firewallActions []string
|
||||
tags []string
|
||||
|
||||
logAttrs map[string]string
|
||||
|
||||
@@ -116,7 +121,7 @@ func (this *HTTPRequest) Do() {
|
||||
// Web配置
|
||||
err := this.configureWeb(this.Server.Web, true, 0)
|
||||
if err != nil {
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
@@ -139,9 +144,10 @@ func (this *HTTPRequest) Do() {
|
||||
|
||||
// 自动跳转到HTTPS
|
||||
if this.IsHTTP && this.web.RedirectToHttps != nil && this.web.RedirectToHttps.IsOn {
|
||||
this.doRedirectToHTTPS(this.web.RedirectToHttps)
|
||||
this.doEnd()
|
||||
return
|
||||
if this.doRedirectToHTTPS(this.web.RedirectToHttps) {
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Gzip
|
||||
@@ -161,6 +167,16 @@ func (this *HTTPRequest) Do() {
|
||||
|
||||
// 开始调用
|
||||
func (this *HTTPRequest) doBegin() {
|
||||
// 特殊URL处理
|
||||
if len(this.rawURI) > 1 && this.rawURI[1] == '.' {
|
||||
// ACME
|
||||
// TODO 需要配置是否启用ACME检测
|
||||
if strings.HasPrefix(this.rawURI, "/.well-known/acme-challenge/") {
|
||||
this.doACME()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 统计
|
||||
if this.web.StatRef != nil && this.web.StatRef.IsOn {
|
||||
this.doStat()
|
||||
@@ -173,16 +189,6 @@ func (this *HTTPRequest) doBegin() {
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊URL处理
|
||||
if len(this.rawURI) > 1 && this.rawURI[1] == '.' {
|
||||
// ACME
|
||||
// TODO 需要配置是否启用ACME检测
|
||||
if strings.HasPrefix(this.rawURI, "/.well-known/acme-challenge/") {
|
||||
this.doACME()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 临时关闭页面
|
||||
if this.web.Shutdown != nil && this.web.Shutdown.IsOn {
|
||||
this.doShutdown()
|
||||
@@ -242,11 +248,20 @@ func (this *HTTPRequest) doEnd() {
|
||||
// TODO 增加是否开启开关
|
||||
if this.Server != nil {
|
||||
if this.isCached {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.writer.sentBodyBytes, this.writer.sentBodyBytes, 1, 1)
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, this.writer.sentBodyBytes, 1, 1, 0, 0)
|
||||
} else {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.writer.sentBodyBytes, 0, 1, 0)
|
||||
if this.isAttack {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 1, this.writer.sentBodyBytes)
|
||||
} else {
|
||||
stats.SharedTrafficStatManager.Add(this.Server.Id, this.Host, this.writer.sentBodyBytes, 0, 1, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 指标
|
||||
if metrics.SharedManager.HasHTTPMetrics() {
|
||||
this.doMetricsResponse()
|
||||
}
|
||||
}
|
||||
|
||||
// RawURI 原始的请求URI
|
||||
@@ -500,6 +515,12 @@ func (this *HTTPRequest) Format(source string) string {
|
||||
return this.requestRemoteUser()
|
||||
case "requestURI", "requestUri":
|
||||
return this.rawURI
|
||||
case "requestURL":
|
||||
var scheme = "http"
|
||||
if this.IsHTTPS {
|
||||
scheme = "https"
|
||||
}
|
||||
return scheme + "://" + this.Host + this.rawURI
|
||||
case "requestPath":
|
||||
return this.requestPath()
|
||||
case "requestPathExtension":
|
||||
@@ -549,6 +570,12 @@ func (this *HTTPRequest) Format(source string) string {
|
||||
return this.Host
|
||||
case "referer":
|
||||
return this.RawReq.Referer()
|
||||
case "referer.host":
|
||||
u, err := url.Parse(this.RawReq.Referer())
|
||||
if err == nil {
|
||||
return u.Host
|
||||
}
|
||||
return ""
|
||||
case "userAgent":
|
||||
return this.RawReq.UserAgent()
|
||||
case "contentType":
|
||||
@@ -1186,5 +1213,10 @@ func (this *HTTPRequest) canIgnore(err error) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// HTTP内部错误
|
||||
if strings.HasPrefix(err.Error(), "http:") || strings.HasPrefix(err.Error(), "http2:") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ func (this *HTTPRequest) doACME() {
|
||||
// TODO 对请求进行校验,防止恶意攻击
|
||||
|
||||
token := filepath.Base(this.RawReq.URL.Path)
|
||||
if token == "acme-challenge" || len(token) <= 32 {
|
||||
this.writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
|
||||
@@ -33,7 +33,7 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
|
||||
return writer.StatusCode(), nil
|
||||
}, this.Format)
|
||||
if err != nil {
|
||||
this.write502(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if b {
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 读取缓存
|
||||
func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
|
||||
cachePolicy := sharedNodeConfig.HTTPCachePolicy
|
||||
cachePolicy := this.Server.HTTPCachePolicy
|
||||
if cachePolicy == nil || !cachePolicy.IsOn {
|
||||
return
|
||||
}
|
||||
@@ -20,6 +21,12 @@ func (this *HTTPRequest) doCacheRead() (shouldStop bool) {
|
||||
if this.web.Cache == nil || !this.web.Cache.IsOn || (len(cachePolicy.CacheRefs) == 0 && len(this.web.Cache.CacheRefs) == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是否在预热
|
||||
if strings.HasPrefix(this.RawReq.RemoteAddr, "127.") && this.RawReq.Header.Get("X-Cache-Action") == "preheat" {
|
||||
return
|
||||
}
|
||||
|
||||
var addStatusHeader = this.web.Cache.AddStatusHeader
|
||||
if addStatusHeader {
|
||||
defer func() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -17,30 +18,15 @@ func (this *HTTPRequest) write404() {
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) write500(err error) {
|
||||
func (this *HTTPRequest) write50x(err error, statusCode int) {
|
||||
if err != nil {
|
||||
this.addError(err)
|
||||
}
|
||||
|
||||
statusCode := http.StatusInternalServerError
|
||||
if this.doPage(statusCode) {
|
||||
return
|
||||
}
|
||||
this.processResponseHeaders(statusCode)
|
||||
this.writer.WriteHeader(statusCode)
|
||||
_, _ = this.writer.Write([]byte(http.StatusText(statusCode)))
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) write502(err error) {
|
||||
if err != nil {
|
||||
this.addError(err)
|
||||
}
|
||||
|
||||
statusCode := http.StatusBadGateway
|
||||
if this.doPage(statusCode) {
|
||||
return
|
||||
}
|
||||
this.processResponseHeaders(statusCode)
|
||||
this.writer.WriteHeader(statusCode)
|
||||
_, _ = this.writer.Write([]byte("502 Bad Gateway"))
|
||||
_, _ = this.writer.Write([]byte(types.String(statusCode) + " " + http.StatusText(statusCode)))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/iwind/gofcgi/pkg/fcgi"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -80,7 +81,7 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
||||
|
||||
client, err := fcgi.SharedPool(fastcgi.Network(), fastcgi.RealAddress(), uint(poolSize)).Client()
|
||||
if err != nil {
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,13 +159,13 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
||||
|
||||
resp, stderr, err := client.Call(fcgiReq)
|
||||
if err != nil {
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(stderr) > 0 {
|
||||
err := errors.New("Fastcgi Error: " + strings.TrimSpace(string(stderr)) + " script: " + maps.NewMap(params).GetString("SCRIPT_FILENAME"))
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,8 @@ func (this *HTTPRequest) log() {
|
||||
FirewallRuleGroupId: this.firewallRuleGroupId,
|
||||
FirewallRuleSetId: this.firewallRuleSetId,
|
||||
FirewallRuleId: this.firewallRuleId,
|
||||
FirewallActions: this.firewallActions,
|
||||
Tags: this.tags,
|
||||
|
||||
Attrs: this.logAttrs,
|
||||
}
|
||||
|
||||
58
internal/nodes/http_request_metrics.go
Normal file
58
internal/nodes/http_request_metrics.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
)
|
||||
|
||||
// 指标统计 - 响应
|
||||
// 只需要在结束时调用指标进行统计
|
||||
func (this *HTTPRequest) doMetricsResponse() {
|
||||
metrics.SharedManager.Add(this)
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) MetricKey(key string) string {
|
||||
return this.Format(key)
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) MetricValue(value string) (result int64, ok bool) {
|
||||
// TODO 需要忽略健康检查的请求,但是同时也要防止攻击者模拟健康检查
|
||||
switch value {
|
||||
case "${countRequest}":
|
||||
return 1, true
|
||||
case "${countTrafficOut}":
|
||||
// 这里不包括Header长度
|
||||
return this.writer.SentBodyBytes(), true
|
||||
case "${countTrafficIn}":
|
||||
var hl int64 = 0 // header length
|
||||
for k, values := range this.RawReq.Header {
|
||||
for _, v := range values {
|
||||
hl += int64(len(k) + len(v) + 2 /** k: v **/)
|
||||
}
|
||||
}
|
||||
return this.RawReq.ContentLength + hl, true
|
||||
case "${countConnection}":
|
||||
metricNewConnMapLocker.Lock()
|
||||
_, ok := metricNewConnMap[this.RawReq.RemoteAddr]
|
||||
if ok {
|
||||
delete(metricNewConnMap, this.RawReq.RemoteAddr)
|
||||
}
|
||||
metricNewConnMapLocker.Unlock()
|
||||
if ok {
|
||||
return 1, true
|
||||
} else {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) MetricServerId() int64 {
|
||||
return this.Server.Id
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) MetricCategory() string {
|
||||
return serverconfigs.MetricItemCategoryHTTP
|
||||
}
|
||||
@@ -7,9 +7,14 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.HTTPRedirectToHTTPSConfig) {
|
||||
func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.HTTPRedirectToHTTPSConfig) (shouldBreak bool) {
|
||||
host := this.RawReq.Host
|
||||
|
||||
// 检查域名是否匹配
|
||||
if !redirectToHTTPSConfig.MatchDomain(host) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(redirectToHTTPSConfig.Host) > 0 {
|
||||
if redirectToHTTPSConfig.Port > 0 && redirectToHTTPSConfig.Port != 443 {
|
||||
host = redirectToHTTPSConfig.Host + ":" + strconv.Itoa(redirectToHTTPSConfig.Port)
|
||||
@@ -38,4 +43,6 @@ func (this *HTTPRequest) doRedirectToHTTPS(redirectToHTTPSConfig *serverconfigs.
|
||||
|
||||
newURL := "https://" + host + this.RawReq.RequestURI
|
||||
http.Redirect(this.writer, this.RawReq, newURL, statusCode)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -36,7 +37,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
if origin == nil {
|
||||
err := errors.New(this.requestPath() + ": no available backends for reverse proxy")
|
||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write502(err)
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
this.origin = origin // 设置全局变量是为了日志等处理
|
||||
@@ -56,7 +57,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
if origin.Addr == nil {
|
||||
err := errors.New(this.requestPath() + ": origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address")
|
||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write502(err)
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme()
|
||||
@@ -143,7 +144,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
client, err := SharedHTTPClientPool.Client(this.RawReq, origin, originAddr)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_REVERSE_PROXY", err.Error())
|
||||
this.write502(err)
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,10 +159,23 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
if err != nil {
|
||||
// 客户端取消请求,则不提示
|
||||
httpErr, ok := err.(*url.Error)
|
||||
if !ok || httpErr.Err != context.Canceled {
|
||||
// TODO 如果超过最大失败次数,则下线
|
||||
|
||||
this.write502(err)
|
||||
if !ok {
|
||||
SharedOriginStateManager.Fail(origin, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+"': "+err.Error())
|
||||
} else if httpErr.Err != context.Canceled {
|
||||
SharedOriginStateManager.Fail(origin, this.reverseProxy, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
if httpErr.Timeout() {
|
||||
this.write50x(err, http.StatusGatewayTimeout)
|
||||
} else if httpErr.Temporary() {
|
||||
this.write50x(err, http.StatusServiceUnavailable)
|
||||
} else {
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
}
|
||||
remotelogs.Warn("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+"': "+err.Error())
|
||||
} else {
|
||||
// 是否为客户端方面的错误
|
||||
@@ -175,7 +189,7 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
}
|
||||
|
||||
if !isClientError {
|
||||
this.write502(err)
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
}
|
||||
}
|
||||
if resp != nil && resp.Body != nil {
|
||||
@@ -183,6 +197,11 @@ func (this *HTTPRequest) doReverseProxy() {
|
||||
}
|
||||
return
|
||||
}
|
||||
if !origin.IsOk {
|
||||
SharedOriginStateManager.Success(origin, func() {
|
||||
this.reverseProxy.ResetScheduling()
|
||||
})
|
||||
}
|
||||
|
||||
// WAF对出站进行检查
|
||||
if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn {
|
||||
|
||||
@@ -109,7 +109,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
}
|
||||
return
|
||||
} else {
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
logs.Error(err)
|
||||
return true
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
}
|
||||
return
|
||||
} else {
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
logs.Error(err)
|
||||
return true
|
||||
}
|
||||
@@ -283,8 +283,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
|
||||
reader, err := os.OpenFile(filePath, os.O_RDONLY, 0444)
|
||||
if err != nil {
|
||||
this.write500(err)
|
||||
logs.Error(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ func (this *HTTPRequest) doStat() {
|
||||
if this.Server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 内置的统计
|
||||
stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.Server.Id, this.requestRemoteAddr())
|
||||
stats.SharedHTTPRequestStatManager.AddUserAgent(this.Server.Id, this.requestHeader("User-Agent"))
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logs.Error(errors.New(req.URL.String() + ": " + err.Error()))
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -25,8 +28,8 @@ func (this *HTTPRequest) doWAFRequest() (blocked bool) {
|
||||
}
|
||||
|
||||
// 公用的防火墙设置
|
||||
if sharedNodeConfig.HTTPFirewallPolicy != nil {
|
||||
blocked, breakChecking := this.checkWAFRequest(sharedNodeConfig.HTTPFirewallPolicy)
|
||||
if this.Server.HTTPFirewallPolicy != nil && this.Server.HTTPFirewallPolicy.IsOn {
|
||||
blocked, breakChecking := this.checkWAFRequest(this.Server.HTTPFirewallPolicy)
|
||||
if blocked {
|
||||
return true
|
||||
}
|
||||
@@ -47,48 +50,55 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
// 检查IP白名单
|
||||
remoteAddrs := this.requestRemoteAddrs()
|
||||
inbound := firewallPolicy.Inbound
|
||||
if inbound.AllowListRef != nil && inbound.AllowListRef.IsOn && inbound.AllowListRef.ListId > 0 {
|
||||
list := iplibrary.SharedIPListManager.FindList(inbound.AllowListRef.ListId)
|
||||
if list != nil {
|
||||
found, _ := list.ContainsIPStrings(remoteAddrs)
|
||||
if found {
|
||||
breakChecking = true
|
||||
return
|
||||
if inbound == nil {
|
||||
return
|
||||
}
|
||||
for _, ref := range inbound.AllAllowListRefs() {
|
||||
if ref.IsOn && ref.ListId > 0 {
|
||||
list := iplibrary.SharedIPListManager.FindList(ref.ListId)
|
||||
if list != nil {
|
||||
found, _ := list.ContainsIPStrings(remoteAddrs)
|
||||
if found {
|
||||
breakChecking = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查IP黑名单
|
||||
if inbound.DenyListRef != nil && inbound.DenyListRef.IsOn && inbound.DenyListRef.ListId > 0 {
|
||||
list := iplibrary.SharedIPListManager.FindList(inbound.DenyListRef.ListId)
|
||||
if list != nil {
|
||||
found, item := list.ContainsIPStrings(remoteAddrs)
|
||||
if found {
|
||||
// 触发事件
|
||||
if item != nil && len(item.EventLevel) > 0 {
|
||||
actions := iplibrary.SharedActionManager.FindEventActions(item.EventLevel)
|
||||
for _, action := range actions {
|
||||
goNext, err := action.DoHTTP(this.RawReq, this.RawWriter)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_WAF", "do action '"+err.Error()+"' failed: "+err.Error())
|
||||
return true, false
|
||||
}
|
||||
if !goNext {
|
||||
this.disableLog = true
|
||||
return true, false
|
||||
for _, ref := range inbound.AllDenyListRefs() {
|
||||
if ref.IsOn && ref.ListId > 0 {
|
||||
list := iplibrary.SharedIPListManager.FindList(ref.ListId)
|
||||
if list != nil {
|
||||
found, item := list.ContainsIPStrings(remoteAddrs)
|
||||
if found {
|
||||
// 触发事件
|
||||
if item != nil && len(item.EventLevel) > 0 {
|
||||
actions := iplibrary.SharedActionManager.FindEventActions(item.EventLevel)
|
||||
for _, action := range actions {
|
||||
goNext, err := action.DoHTTP(this.RawReq, this.RawWriter)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_WAF", "do action '"+err.Error()+"' failed: "+err.Error())
|
||||
return true, false
|
||||
}
|
||||
if !goNext {
|
||||
this.disableLog = true
|
||||
return true, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 需要记录日志信息
|
||||
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
this.writer.Close()
|
||||
|
||||
// 停止日志
|
||||
this.disableLog = true
|
||||
|
||||
return true, false
|
||||
}
|
||||
|
||||
// TODO 需要记录日志信息
|
||||
|
||||
this.writer.WriteHeader(http.StatusForbidden)
|
||||
this.writer.Close()
|
||||
|
||||
// 停止日志
|
||||
this.disableLog = true
|
||||
|
||||
return true, false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,23 +155,36 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
goNext, ruleGroup, ruleSet, err := w.MatchRequest(this.RawReq, this.writer)
|
||||
|
||||
w.OnAction(func(action waf.ActionInterface) (goNext bool) {
|
||||
switch action.Code() {
|
||||
case waf.ActionTag:
|
||||
this.tags = action.(*waf.TagAction).Tags
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
goNext, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_WAF", this.rawURI+": "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if ruleSet != nil {
|
||||
if ruleSet.Action != waf.ActionAllow {
|
||||
if ruleSet.HasSpecialActions() {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
this.firewallRuleGroupId = types.Int64(ruleGroup.Id)
|
||||
this.firewallRuleSetId = types.Int64(ruleSet.Id)
|
||||
|
||||
if ruleSet.HasAttackActions() {
|
||||
this.isAttack = true
|
||||
}
|
||||
|
||||
// 添加统计
|
||||
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.Server.Id, this.firewallRuleGroupId, ruleSet.Action)
|
||||
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.Server.Id, this.firewallRuleGroupId, ruleSet.Actions)
|
||||
}
|
||||
|
||||
this.logAttrs["waf.action"] = ruleSet.Action
|
||||
this.firewallActions = ruleSet.ActionCodes()
|
||||
}
|
||||
|
||||
return !goNext, false
|
||||
@@ -169,7 +192,25 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
||||
|
||||
// call response waf
|
||||
func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
|
||||
firewallPolicy := sharedNodeConfig.HTTPFirewallPolicy
|
||||
// 当前服务的独立设置
|
||||
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
|
||||
blocked := this.checkWAFResponse(this.web.FirewallPolicy, resp)
|
||||
if blocked {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 公用的防火墙设置
|
||||
if this.Server.HTTPFirewallPolicy != nil && this.Server.HTTPFirewallPolicy.IsOn {
|
||||
blocked := this.checkWAFResponse(this.Server.HTTPFirewallPolicy, resp)
|
||||
if blocked {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFirewallPolicy, resp *http.Response) (blocked bool) {
|
||||
if firewallPolicy == nil || !firewallPolicy.IsOn || !firewallPolicy.Outbound.IsOn {
|
||||
return
|
||||
}
|
||||
@@ -179,24 +220,79 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
|
||||
return
|
||||
}
|
||||
|
||||
goNext, ruleGroup, ruleSet, err := w.MatchResponse(this.RawReq, resp, this.writer)
|
||||
w.OnAction(func(action waf.ActionInterface) (goNext bool) {
|
||||
switch action.Code() {
|
||||
case waf.ActionTag:
|
||||
this.tags = action.(*waf.TagAction).Tags
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
goNext, ruleGroup, ruleSet, err := w.MatchResponse(this, resp, this.writer)
|
||||
if err != nil {
|
||||
remotelogs.Error("HTTP_REQUEST_WAF", this.rawURI+": "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if ruleSet != nil {
|
||||
if ruleSet.Action != waf.ActionAllow {
|
||||
if ruleSet.HasSpecialActions() {
|
||||
this.firewallPolicyId = firewallPolicy.Id
|
||||
this.firewallRuleGroupId = types.Int64(ruleGroup.Id)
|
||||
this.firewallRuleSetId = types.Int64(ruleSet.Id)
|
||||
|
||||
if ruleSet.HasAttackActions() {
|
||||
this.isAttack = true
|
||||
}
|
||||
|
||||
// 添加统计
|
||||
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.Server.Id, this.firewallRuleGroupId, ruleSet.Action)
|
||||
stats.SharedHTTPRequestStatManager.AddFirewallRuleGroupId(this.Server.Id, this.firewallRuleGroupId, ruleSet.Actions)
|
||||
}
|
||||
|
||||
this.logAttrs["waf.action"] = ruleSet.Action
|
||||
this.firewallActions = ruleSet.ActionCodes()
|
||||
}
|
||||
|
||||
return !goNext
|
||||
}
|
||||
|
||||
// WAFRaw 原始请求
|
||||
func (this *HTTPRequest) WAFRaw() *http.Request {
|
||||
return this.RawReq
|
||||
}
|
||||
|
||||
// WAFRemoteIP 客户端IP
|
||||
func (this *HTTPRequest) WAFRemoteIP() string {
|
||||
return this.requestRemoteAddr()
|
||||
}
|
||||
|
||||
// WAFGetCacheBody 获取缓存中的Body
|
||||
func (this *HTTPRequest) WAFGetCacheBody() []byte {
|
||||
return this.bodyData
|
||||
}
|
||||
|
||||
// WAFSetCacheBody 设置Body
|
||||
func (this *HTTPRequest) WAFSetCacheBody(body []byte) {
|
||||
this.bodyData = body
|
||||
}
|
||||
|
||||
// WAFReadBody 读取Body
|
||||
func (this *HTTPRequest) WAFReadBody(max int64) (data []byte, err error) {
|
||||
if this.RawReq.ContentLength > 0 {
|
||||
data, err = ioutil.ReadAll(io.LimitReader(this.RawReq.Body, max))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WAFRestoreBody 恢复Body
|
||||
func (this *HTTPRequest) WAFRestoreBody(data []byte) {
|
||||
if len(data) > 0 {
|
||||
rawReader := bytes.NewBuffer(data)
|
||||
buf := make([]byte, 1024)
|
||||
_, _ = io.CopyBuffer(rawReader, this.RawReq.Body, buf)
|
||||
this.RawReq.Body = ioutil.NopCloser(rawReader)
|
||||
}
|
||||
}
|
||||
|
||||
// WAFServerId 服务ID
|
||||
func (this *HTTPRequest) WAFServerId() int64 {
|
||||
return this.Server.Id
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -43,8 +42,7 @@ func (this *HTTPRequest) doWebsocket() {
|
||||
// TODO 增加N次错误重试,重试的时候需要尝试不同的源站
|
||||
originConn, err := OriginConnect(this.origin, this.RawReq.RemoteAddr)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
@@ -53,15 +51,13 @@ func (this *HTTPRequest) doWebsocket() {
|
||||
|
||||
err = this.RawReq.Write(originConn)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
clientConn, _, err := this.writer.Hijack()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
this.write500(err)
|
||||
this.write50x(err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -223,17 +224,29 @@ func (this *HTTPWriter) Close() {
|
||||
// cache writer
|
||||
if this.cacheWriter != nil {
|
||||
if this.isOk {
|
||||
err := this.cacheWriter.Close()
|
||||
if err == nil {
|
||||
this.cacheStorage.AddToList(&caches.Item{
|
||||
Type: this.cacheWriter.ItemType(),
|
||||
Key: this.cacheWriter.Key(),
|
||||
ExpiredAt: this.cacheWriter.ExpiredAt(),
|
||||
HeaderSize: this.cacheWriter.HeaderSize(),
|
||||
BodySize: this.cacheWriter.BodySize(),
|
||||
Host: this.req.Host,
|
||||
ServerId: this.req.Server.Id,
|
||||
})
|
||||
// 对比Content-Length
|
||||
contentLengthString := this.Header().Get("Content-Length")
|
||||
if len(contentLengthString) > 0 {
|
||||
contentLength := types.Int64(contentLengthString)
|
||||
if contentLength != this.cacheWriter.BodySize() {
|
||||
this.isOk = false
|
||||
_ = this.cacheWriter.Discard()
|
||||
}
|
||||
}
|
||||
|
||||
if this.isOk {
|
||||
err := this.cacheWriter.Close()
|
||||
if err == nil {
|
||||
this.cacheStorage.AddToList(&caches.Item{
|
||||
Type: this.cacheWriter.ItemType(),
|
||||
Key: this.cacheWriter.Key(),
|
||||
ExpiredAt: this.cacheWriter.ExpiredAt(),
|
||||
HeaderSize: this.cacheWriter.HeaderSize(),
|
||||
BodySize: this.cacheWriter.BodySize(),
|
||||
Host: this.req.Host,
|
||||
ServerId: this.req.Server.Id,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_ = this.cacheWriter.Discard()
|
||||
@@ -329,7 +342,7 @@ func (this *HTTPWriter) prepareCache(size int64) {
|
||||
return
|
||||
}
|
||||
|
||||
cachePolicy := sharedNodeConfig.HTTPCachePolicy
|
||||
cachePolicy := this.req.Server.HTTPCachePolicy
|
||||
if cachePolicy == nil || !cachePolicy.IsOn {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
http2 "golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,11 +9,14 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpErrorLogger = log.New(io.Discard, "", 0)
|
||||
var metricNewConnMap = map[string]bool{} // remoteAddr => bool
|
||||
var metricNewConnMapLocker = &sync.Mutex{}
|
||||
|
||||
type HTTPListener struct {
|
||||
BaseListener
|
||||
@@ -39,15 +42,27 @@ func (this *HTTPListener) Serve() error {
|
||||
this.httpServer = &http.Server{
|
||||
Addr: this.addr,
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 3 * time.Second, // TODO 改成可以配置
|
||||
ReadHeaderTimeout: 2 * time.Second, // TODO 改成可以配置
|
||||
IdleTimeout: 2 * time.Minute, // TODO 改成可以配置
|
||||
ErrorLog: httpErrorLogger,
|
||||
ConnState: func(conn net.Conn, state http.ConnState) {
|
||||
switch state {
|
||||
case http.StateNew:
|
||||
atomic.AddInt64(&this.countActiveConnections, 1)
|
||||
|
||||
// 为指标存储连接信息
|
||||
if sharedNodeConfig.HasHTTPConnectionMetrics() {
|
||||
metricNewConnMapLocker.Lock()
|
||||
metricNewConnMap[conn.RemoteAddr().String()] = true
|
||||
metricNewConnMapLocker.Unlock()
|
||||
}
|
||||
case http.StateClosed:
|
||||
atomic.AddInt64(&this.countActiveConnections, -1)
|
||||
|
||||
// 移除指标存储连接信息
|
||||
metricNewConnMapLocker.Lock()
|
||||
delete(metricNewConnMap, conn.RemoteAddr().String())
|
||||
metricNewConnMapLocker.Unlock()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (this *TCPListener) handleConn(conn net.Conn) error {
|
||||
}
|
||||
|
||||
// 记录流量
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, int64(n), 0, 0, 0)
|
||||
stats.SharedTrafficStatManager.Add(firstServer.Id, "", int64(n), 0, 0, 0, 0, 0)
|
||||
}
|
||||
if err != nil {
|
||||
closer()
|
||||
|
||||
@@ -164,7 +164,7 @@ func NewUDPConn(serverId int64, addr net.Addr, proxyConn *net.UDPConn, serverCon
|
||||
}
|
||||
|
||||
// 记录流量
|
||||
stats.SharedTrafficStatManager.Add(serverId, int64(n), 0, 0, 0)
|
||||
stats.SharedTrafficStatManager.Add(serverId, "", int64(n), 0, 0, 0, 0, 0)
|
||||
}
|
||||
if err != nil {
|
||||
conn.isOk = false
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/apps"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/stats"
|
||||
@@ -20,14 +20,13 @@ import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -39,10 +38,13 @@ var DaemonPid = 0
|
||||
// Node 节点
|
||||
type Node struct {
|
||||
isLoaded bool
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewNode() *Node {
|
||||
return &Node{}
|
||||
return &Node{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
// Test 检查配置
|
||||
@@ -72,9 +74,6 @@ func (this *Node) Start() {
|
||||
// 启动事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
// 处理信号
|
||||
this.listenSignals()
|
||||
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
@@ -151,24 +150,16 @@ func (this *Node) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
// 写入PID
|
||||
err = apps.WritePid()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "write pid failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// hold住进程
|
||||
select {}
|
||||
}
|
||||
|
||||
// Daemon 实现守护进程
|
||||
func (this *Node) Daemon() {
|
||||
path := os.TempDir() + "/edge-node.sock"
|
||||
isDebug := lists.ContainsString(os.Args, "debug")
|
||||
isDebug = true
|
||||
for {
|
||||
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
||||
conn, err := this.sock.Dial()
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
@@ -228,32 +219,6 @@ func (this *Node) InstallSystemService() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理信号
|
||||
func (this *Node) listenSignals() {
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, syscall.SIGQUIT)
|
||||
go func() {
|
||||
for s := range signals {
|
||||
switch s {
|
||||
case syscall.SIGQUIT:
|
||||
events.Notify(events.EventQuit)
|
||||
|
||||
// 监控连接数,如果连接数为0,则退出进程
|
||||
go func() {
|
||||
for {
|
||||
countActiveConnections := sharedListenerManager.TotalActiveConnections()
|
||||
if countActiveConnections <= 0 {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 循环
|
||||
func (this *Node) loop() error {
|
||||
// 检查api.yaml是否存在
|
||||
@@ -382,12 +347,12 @@ func (this *Node) syncConfig() error {
|
||||
} else {
|
||||
remotelogs.Println("NODE", "loading config ...")
|
||||
}
|
||||
|
||||
|
||||
nodeconfigs.ResetNodeConfig(nodeConfig)
|
||||
caches.SharedManager.MaxDiskCapacity = nodeConfig.MaxCacheDiskCapacity
|
||||
caches.SharedManager.MaxMemoryCapacity = nodeConfig.MaxCacheMemoryCapacity
|
||||
if nodeConfig.HTTPCachePolicy != nil {
|
||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{nodeConfig.HTTPCachePolicy})
|
||||
if len(nodeConfig.HTTPCachePolicies) > 0 {
|
||||
caches.SharedManager.UpdatePolicies(nodeConfig.HTTPCachePolicies)
|
||||
} else {
|
||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
||||
}
|
||||
@@ -396,6 +361,8 @@ func (this *Node) syncConfig() error {
|
||||
iplibrary.SharedActionManager.UpdateActions(nodeConfig.FirewallActions)
|
||||
sharedNodeConfig = nodeConfig
|
||||
|
||||
metrics.SharedManager.Update(nodeConfig.MetricItems)
|
||||
|
||||
// 发送事件
|
||||
events.Notify(events.EventReload)
|
||||
|
||||
@@ -490,37 +457,63 @@ func (this *Node) checkClusterConfig() error {
|
||||
|
||||
// 监听本地sock
|
||||
func (this *Node) listenSock() error {
|
||||
path := os.TempDir() + "/edge-node.sock"
|
||||
|
||||
// 检查是否已经存在
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
conn, err := net.Dial("unix", path)
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 新的监听任务
|
||||
listener, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events.On(events.EventQuit, func() {
|
||||
remotelogs.Println("NODE", "quit unix sock")
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
for {
|
||||
_, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
case "quit":
|
||||
_ = cmd.ReplyOk()
|
||||
_ = this.sock.Close()
|
||||
|
||||
events.Notify(events.EventQuit)
|
||||
|
||||
// 监控连接数,如果连接数为0,则退出进程
|
||||
go func() {
|
||||
for {
|
||||
countActiveConnections := sharedListenerManager.TotalActiveConnections()
|
||||
if countActiveConnections <= 0 {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -67,6 +68,8 @@ func (this *NodeStatusExecutor) update() {
|
||||
status.ConnectionCount = sharedListenerManager.TotalActiveConnections()
|
||||
status.CacheTotalDiskSize = caches.SharedManager.TotalDiskSize()
|
||||
status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize()
|
||||
status.TrafficInBytes = inTrafficBytes
|
||||
status.TrafficOutBytes = outTrafficBytes
|
||||
|
||||
// 记录监控数据
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemConnections, maps.Map{
|
||||
@@ -80,6 +83,7 @@ func (this *NodeStatusExecutor) update() {
|
||||
this.updateMem(status)
|
||||
this.updateLoad(status)
|
||||
this.updateDisk(status)
|
||||
this.updateCacheSpace(status)
|
||||
status.UpdatedAt = time.Now().Unix()
|
||||
|
||||
// 发送数据
|
||||
@@ -211,3 +215,25 @@ func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
|
||||
"maxUsage": status.DiskMaxUsage,
|
||||
})
|
||||
}
|
||||
|
||||
// 缓存空间
|
||||
func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus) {
|
||||
var result = []maps.Map{}
|
||||
cachePaths := caches.SharedManager.FindAllCachePaths()
|
||||
for _, path := range cachePaths {
|
||||
var stat unix.Statfs_t
|
||||
err := unix.Statfs(path, &stat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result = append(result, maps.Map{
|
||||
"path": path,
|
||||
"total": stat.Blocks * uint64(stat.Bsize),
|
||||
"avail": stat.Bavail * uint64(stat.Bsize),
|
||||
"used": (stat.Blocks - stat.Bavail) * uint64(stat.Bsize),
|
||||
})
|
||||
}
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemCacheDir, maps.Map{
|
||||
"dirs": result,
|
||||
})
|
||||
}
|
||||
|
||||
12
internal/nodes/origin_state.go
Normal file
12
internal/nodes/origin_state.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
|
||||
type OriginState struct {
|
||||
CountFails int64
|
||||
UpdatedAt int64
|
||||
Config *serverconfigs.OriginConfig
|
||||
ReverseProxy *serverconfigs.ReverseProxyConfig
|
||||
}
|
||||
174
internal/nodes/origin_state_manager.go
Normal file
174
internal/nodes/origin_state_manager.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var SharedOriginStateManager = NewOriginStateManager()
|
||||
|
||||
func init() {
|
||||
events.On(events.EventLoaded, func() {
|
||||
go SharedOriginStateManager.Start()
|
||||
})
|
||||
}
|
||||
|
||||
// OriginStateManager 源站状态管理
|
||||
type OriginStateManager struct {
|
||||
stateMap map[int64]*OriginState // originId => *OriginState
|
||||
|
||||
ticker *time.Ticker
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
// NewOriginStateManager 获取新管理对象
|
||||
func NewOriginStateManager() *OriginStateManager {
|
||||
return &OriginStateManager{
|
||||
stateMap: map[int64]*OriginState{},
|
||||
ticker: time.NewTicker(60 * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动
|
||||
func (this *OriginStateManager) Start() {
|
||||
events.On(events.EventReload, func() {
|
||||
this.locker.Lock()
|
||||
this.stateMap = map[int64]*OriginState{}
|
||||
this.locker.Unlock()
|
||||
})
|
||||
|
||||
if Tea.IsTesting() {
|
||||
this.ticker = time.NewTicker(10 * time.Second)
|
||||
}
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("ORIGIN_MANAGER", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop 单次循环检查
|
||||
func (this *OriginStateManager) Loop() error {
|
||||
if sharedNodeConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentStates = []*OriginState{}
|
||||
this.locker.Lock()
|
||||
for originId, state := range this.stateMap {
|
||||
// 检查Origin是否正在使用
|
||||
config := sharedNodeConfig.FindOrigin(originId)
|
||||
if config == nil || !config.IsOn {
|
||||
delete(this.stateMap, originId)
|
||||
continue
|
||||
}
|
||||
state.Config = config
|
||||
currentStates = append(currentStates, state)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
|
||||
if len(currentStates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var count = len(currentStates)
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(count)
|
||||
for _, state := range currentStates {
|
||||
go func(state *OriginState) {
|
||||
defer wg.Done()
|
||||
conn, err := OriginConnect(state.Config, "")
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
|
||||
// 已经恢复正常
|
||||
this.locker.Lock()
|
||||
state.Config.IsOk = true
|
||||
delete(this.stateMap, state.Config.Id)
|
||||
this.locker.Unlock()
|
||||
|
||||
var reverseProxy = state.ReverseProxy
|
||||
if reverseProxy != nil {
|
||||
reverseProxy.ResetScheduling()
|
||||
}
|
||||
}
|
||||
}(state)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fail 添加失败的源站
|
||||
func (this *OriginStateManager) Fail(origin *serverconfigs.OriginConfig, reverseProxy *serverconfigs.ReverseProxyConfig, callback func()) {
|
||||
if origin == nil {
|
||||
return
|
||||
}
|
||||
this.locker.Lock()
|
||||
state, ok := this.stateMap[origin.Id]
|
||||
var timestamp = time.Now().Unix()
|
||||
if ok {
|
||||
if state.UpdatedAt < timestamp-300 { // N 分钟之后重新计数
|
||||
state.CountFails = 0
|
||||
state.Config.IsOk = true
|
||||
}
|
||||
|
||||
state.CountFails++
|
||||
state.Config = origin
|
||||
state.ReverseProxy = reverseProxy
|
||||
state.UpdatedAt = timestamp
|
||||
|
||||
if origin.IsOk {
|
||||
origin.IsOk = state.CountFails > 5 // 超过 N 次之后认为是异常
|
||||
|
||||
if !origin.IsOk {
|
||||
if callback != nil {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.stateMap[origin.Id] = &OriginState{
|
||||
CountFails: 1,
|
||||
Config: origin,
|
||||
ReverseProxy: reverseProxy,
|
||||
UpdatedAt: timestamp,
|
||||
}
|
||||
origin.IsOk = true
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// Success 添加成功的源站
|
||||
func (this *OriginStateManager) Success(origin *serverconfigs.OriginConfig, callback func()) {
|
||||
if origin == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !origin.IsOk {
|
||||
if callback != nil {
|
||||
defer callback()
|
||||
}
|
||||
}
|
||||
|
||||
origin.IsOk = true
|
||||
this.locker.Lock()
|
||||
delete(this.stateMap, origin.Id)
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// IsAvailable 检查是否正常
|
||||
func (this *OriginStateManager) IsAvailable(originId int64) bool {
|
||||
this.locker.RLock()
|
||||
_, ok := this.stateMap[originId]
|
||||
this.locker.RUnlock()
|
||||
|
||||
return !ok
|
||||
}
|
||||
15
internal/nodes/origin_state_manager_test.go
Normal file
15
internal/nodes/origin_state_manager_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOriginManager_Loop(t *testing.T) {
|
||||
var manager = NewOriginStateManager()
|
||||
err := manager.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(manager.stateMap)
|
||||
}
|
||||
@@ -16,41 +16,44 @@ func OriginConnect(origin *serverconfigs.OriginConfig, remoteAddr string) (net.C
|
||||
}
|
||||
|
||||
// 支持TOA的连接
|
||||
toaConfig := sharedTOAManager.Config()
|
||||
if toaConfig != nil && toaConfig.IsOn {
|
||||
retries := 3
|
||||
for i := 1; i <= retries; i++ {
|
||||
port := int(toaConfig.RandLocalPort())
|
||||
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + remoteAddr)
|
||||
if err != nil {
|
||||
remotelogs.Error("TOA", "add failed: "+err.Error())
|
||||
} else {
|
||||
dialer := net.Dialer{
|
||||
Timeout: origin.ConnTimeoutDuration(),
|
||||
LocalAddr: &net.TCPAddr{
|
||||
Port: port,
|
||||
},
|
||||
}
|
||||
var conn net.Conn
|
||||
switch origin.Addr.Protocol {
|
||||
case "", serverconfigs.ProtocolTCP, serverconfigs.ProtocolHTTP:
|
||||
// TODO 支持TCP4/TCP6
|
||||
// TODO 支持指定特定网卡
|
||||
// TODO Addr支持端口范围,如果有多个端口时,随机一个端口使用
|
||||
conn, err = dialer.Dial("tcp", origin.Addr.Host+":"+origin.Addr.PortRange)
|
||||
case serverconfigs.ProtocolTLS, serverconfigs.ProtocolHTTPS:
|
||||
// TODO 支持TCP4/TCP6
|
||||
// TODO 支持指定特定网卡
|
||||
// TODO Addr支持端口范围,如果有多个端口时,随机一个端口使用
|
||||
// TODO 支持使用证书
|
||||
conn, err = tls.DialWithDialer(&dialer, "tcp", origin.Addr.Host+":"+origin.Addr.PortRange, &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}
|
||||
// 这个条件很重要,如果没有传递remoteAddr,表示不使用TOA
|
||||
if len(remoteAddr) > 0 {
|
||||
toaConfig := sharedTOAManager.Config()
|
||||
if toaConfig != nil && toaConfig.IsOn {
|
||||
retries := 3
|
||||
for i := 1; i <= retries; i++ {
|
||||
port := int(toaConfig.RandLocalPort())
|
||||
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + remoteAddr)
|
||||
if err != nil {
|
||||
remotelogs.Error("TOA", "add failed: "+err.Error())
|
||||
} else {
|
||||
dialer := net.Dialer{
|
||||
Timeout: origin.ConnTimeoutDuration(),
|
||||
LocalAddr: &net.TCPAddr{
|
||||
Port: port,
|
||||
},
|
||||
}
|
||||
var conn net.Conn
|
||||
switch origin.Addr.Protocol {
|
||||
case "", serverconfigs.ProtocolTCP, serverconfigs.ProtocolHTTP:
|
||||
// TODO 支持TCP4/TCP6
|
||||
// TODO 支持指定特定网卡
|
||||
// TODO Addr支持端口范围,如果有多个端口时,随机一个端口使用
|
||||
conn, err = dialer.Dial("tcp", origin.Addr.Host+":"+origin.Addr.PortRange)
|
||||
case serverconfigs.ProtocolTLS, serverconfigs.ProtocolHTTPS:
|
||||
// TODO 支持TCP4/TCP6
|
||||
// TODO 支持指定特定网卡
|
||||
// TODO Addr支持端口范围,如果有多个端口时,随机一个端口使用
|
||||
// TODO 支持使用证书
|
||||
conn, err = tls.DialWithDialer(&dialer, "tcp", origin.Addr.Host+":"+origin.Addr.PortRange, &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO 需要在合适的时机删除TOA记录
|
||||
if err == nil || i == retries {
|
||||
return conn, err
|
||||
// TODO 需要在合适的时机删除TOA记录
|
||||
if err == nil || i == retries {
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -8,8 +10,12 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -20,7 +26,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
// API节点同步任务
|
||||
// SyncAPINodesTask API节点同步任务
|
||||
type SyncAPINodesTask struct {
|
||||
}
|
||||
|
||||
@@ -74,6 +80,12 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 测试是否有API节点可用
|
||||
hasOk := this.testEndpoints(newEndpoints)
|
||||
if !hasOk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPC.Endpoints = newEndpoints
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
@@ -95,3 +107,47 @@ func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) b
|
||||
sort.Strings(endpoints2)
|
||||
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
|
||||
if len(endpoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
wg.Add(len(endpoints))
|
||||
|
||||
var ok = false
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
go func(endpoint string) {
|
||||
defer wg.Done()
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer func() {
|
||||
cancel()
|
||||
}()
|
||||
var conn *grpc.ClientConn
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithInsecure(), grpc.WithBlock())
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithBlock())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.Close()
|
||||
|
||||
ok = true
|
||||
}(endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ func (this *TOAManager) SendMsg(msg string) error {
|
||||
if this.conn != nil {
|
||||
_, err := this.conn.Write([]byte(msg + "\n"))
|
||||
if err != nil {
|
||||
_ = this.conn.Close()
|
||||
this.conn = nil
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package nodes
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"net"
|
||||
)
|
||||
|
||||
// TrafficListener 用于统计流量的网络监听
|
||||
type TrafficListener struct {
|
||||
@@ -18,6 +21,17 @@ func (this *TrafficListener) Accept() (net.Conn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 是否在WAF名单中
|
||||
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err == nil {
|
||||
if !waf.SharedIPWhiteList.Contains(waf.IPTypeAll, ip) && waf.SharedIPBlackLIst.Contains(waf.IPTypeAll, ip) {
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
return NewTrafficConn(conn), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,20 +11,20 @@ import (
|
||||
|
||||
var sharedWAFManager = NewWAFManager()
|
||||
|
||||
// WAF管理器
|
||||
// WAFManager WAF管理器
|
||||
type WAFManager struct {
|
||||
mapping map[int64]*waf.WAF // policyId => WAF
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
// NewWAFManager 获取新对象
|
||||
func NewWAFManager() *WAFManager {
|
||||
return &WAFManager{
|
||||
mapping: map[int64]*waf.WAF{},
|
||||
}
|
||||
}
|
||||
|
||||
// 更新策略
|
||||
// UpdatePolicies 更新策略
|
||||
func (this *WAFManager) UpdatePolicies(policies []*firewallconfigs.HTTPFirewallPolicy) {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
@@ -44,7 +44,7 @@ func (this *WAFManager) UpdatePolicies(policies []*firewallconfigs.HTTPFirewallP
|
||||
this.mapping = m
|
||||
}
|
||||
|
||||
// 查找WAF
|
||||
// FindWAF 查找WAF
|
||||
func (this *WAFManager) FindWAF(policyId int64) *waf.WAF {
|
||||
this.locker.RLock()
|
||||
w, _ := this.mapping[policyId]
|
||||
@@ -78,14 +78,15 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
// rule sets
|
||||
for _, set := range group.Sets {
|
||||
s := &waf.RuleSet{
|
||||
Id: strconv.FormatInt(set.Id, 10),
|
||||
Code: set.Code,
|
||||
IsOn: set.IsOn,
|
||||
Name: set.Name,
|
||||
Description: set.Description,
|
||||
Connector: set.Connector,
|
||||
Action: set.Action,
|
||||
ActionOptions: set.ActionOptions,
|
||||
Id: strconv.FormatInt(set.Id, 10),
|
||||
Code: set.Code,
|
||||
IsOn: set.IsOn,
|
||||
Name: set.Name,
|
||||
Description: set.Description,
|
||||
Connector: set.Connector,
|
||||
}
|
||||
for _, a := range set.Actions {
|
||||
s.AddAction(a.Code, a.Options)
|
||||
}
|
||||
|
||||
// rules
|
||||
@@ -132,14 +133,16 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
// rule sets
|
||||
for _, set := range group.Sets {
|
||||
s := &waf.RuleSet{
|
||||
Id: strconv.FormatInt(set.Id, 10),
|
||||
Code: set.Code,
|
||||
IsOn: set.IsOn,
|
||||
Name: set.Name,
|
||||
Description: set.Description,
|
||||
Connector: set.Connector,
|
||||
Action: set.Action,
|
||||
ActionOptions: set.ActionOptions,
|
||||
Id: strconv.FormatInt(set.Id, 10),
|
||||
Code: set.Code,
|
||||
IsOn: set.IsOn,
|
||||
Name: set.Name,
|
||||
Description: set.Description,
|
||||
Connector: set.Connector,
|
||||
}
|
||||
|
||||
for _, a := range set.Actions {
|
||||
s.AddAction(a.Code, a.Options)
|
||||
}
|
||||
|
||||
// rules
|
||||
@@ -164,10 +167,11 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
||||
|
||||
// action
|
||||
if policy.BlockOptions != nil {
|
||||
w.ActionBlock = &waf.BlockAction{
|
||||
w.DefaultBlockAction = &waf.BlockAction{
|
||||
StatusCode: policy.BlockOptions.StatusCode,
|
||||
Body: policy.BlockOptions.Body,
|
||||
URL: "",
|
||||
URL: policy.BlockOptions.URL,
|
||||
Timeout: policy.BlockOptions.Timeout,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,14 @@ func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
|
||||
return pb.NewServerDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
|
||||
return pb.NewMetricStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FirewallService() pb.FirewallServiceClient {
|
||||
return pb.NewFirewallServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 节点上下文信息
|
||||
func (this *RPCClient) Context() context.Context {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/mssola/user_agent"
|
||||
@@ -17,7 +21,7 @@ import (
|
||||
|
||||
var SharedHTTPRequestStatManager = NewHTTPRequestStatManager()
|
||||
|
||||
// HTTP请求相关的统计
|
||||
// HTTPRequestStatManager HTTP请求相关的统计
|
||||
// 这里的统计是一个辅助统计,注意不要因为统计而影响服务工作性能
|
||||
type HTTPRequestStatManager struct {
|
||||
ipChan chan string
|
||||
@@ -30,24 +34,39 @@ type HTTPRequestStatManager struct {
|
||||
browserMap map[string]int64 // serverId@browser@version => count
|
||||
|
||||
dailyFirewallRuleGroupMap map[string]int64 // serverId@firewallRuleGroupId@action => count
|
||||
|
||||
totalAttackRequests int64
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
// NewHTTPRequestStatManager 获取新对象
|
||||
func NewHTTPRequestStatManager() *HTTPRequestStatManager {
|
||||
return &HTTPRequestStatManager{
|
||||
ipChan: make(chan string, 10_000), // TODO 将来可以配置容量
|
||||
userAgentChan: make(chan string, 10_000), // TODO 将来可以配置容量
|
||||
firewallRuleGroupChan: make(chan string, 10_000), // TODO 将来可以配置容量
|
||||
cityMap: map[string]int64{},
|
||||
providerMap: map[string]int64{},
|
||||
systemMap: map[string]int64{},
|
||||
browserMap: map[string]int64{},
|
||||
cityMap: map[string]int64{},
|
||||
providerMap: map[string]int64{},
|
||||
systemMap: map[string]int64{},
|
||||
browserMap: map[string]int64{},
|
||||
dailyFirewallRuleGroupMap: map[string]int64{},
|
||||
}
|
||||
}
|
||||
|
||||
// 启动
|
||||
// Start 启动
|
||||
func (this *HTTPRequestStatManager) Start() {
|
||||
// 上传请求总数
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
if this.totalAttackRequests > 0 {
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAttackRequests, maps.Map{"total": this.totalAttackRequests})
|
||||
this.totalAttackRequests = 0
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
loopTicker := time.NewTicker(1 * time.Second)
|
||||
uploadTicker := time.NewTicker(30 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
@@ -76,7 +95,7 @@ func (this *HTTPRequestStatManager) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加客户端地址
|
||||
// AddRemoteAddr 添加客户端地址
|
||||
func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr string) {
|
||||
if len(remoteAddr) == 0 {
|
||||
return
|
||||
@@ -100,7 +119,7 @@ func (this *HTTPRequestStatManager) AddRemoteAddr(serverId int64, remoteAddr str
|
||||
}
|
||||
}
|
||||
|
||||
// 添加UserAgent
|
||||
// AddUserAgent 添加UserAgent
|
||||
func (this *HTTPRequestStatManager) AddUserAgent(serverId int64, userAgent string) {
|
||||
if len(userAgent) == 0 {
|
||||
return
|
||||
@@ -113,19 +132,24 @@ func (this *HTTPRequestStatManager) AddUserAgent(serverId int64, userAgent strin
|
||||
}
|
||||
}
|
||||
|
||||
// 添加防火墙拦截动作
|
||||
func (this *HTTPRequestStatManager) AddFirewallRuleGroupId(serverId int64, firewallRuleGroupId int64, action string) {
|
||||
// AddFirewallRuleGroupId 添加防火墙拦截动作
|
||||
func (this *HTTPRequestStatManager) AddFirewallRuleGroupId(serverId int64, firewallRuleGroupId int64, actions []*waf.ActionConfig) {
|
||||
if firewallRuleGroupId <= 0 {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case this.firewallRuleGroupChan <- strconv.FormatInt(serverId, 10) + "@" + strconv.FormatInt(firewallRuleGroupId, 10) + "@" + action:
|
||||
default:
|
||||
// 超出容量我们就丢弃
|
||||
|
||||
this.totalAttackRequests++
|
||||
|
||||
for _, action := range actions {
|
||||
select {
|
||||
case this.firewallRuleGroupChan <- strconv.FormatInt(serverId, 10) + "@" + strconv.FormatInt(firewallRuleGroupId, 10) + "@" + action.Code:
|
||||
default:
|
||||
// 超出容量我们就丢弃
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 单个循环
|
||||
// Loop 单个循环
|
||||
func (this *HTTPRequestStatManager) Loop() error {
|
||||
timeout := time.NewTimer(10 * time.Minute) // 执行的最大时间
|
||||
userAgentParser := &user_agent.UserAgent{}
|
||||
@@ -141,7 +165,7 @@ Loop:
|
||||
ip := ipString[atIndex+1:]
|
||||
if iplibrary.SharedLibrary != nil {
|
||||
result, err := iplibrary.SharedLibrary.Lookup(ip)
|
||||
if err == nil {
|
||||
if err == nil && result != nil {
|
||||
this.cityMap[serverId+"@"+result.Country+"@"+result.Province+"@"+result.City] ++
|
||||
|
||||
if len(result.ISP) > 0 {
|
||||
@@ -189,6 +213,7 @@ Loop:
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload 上传数据
|
||||
func (this *HTTPRequestStatManager) Upload() error {
|
||||
// 上传统计数据
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -20,19 +24,25 @@ type TrafficItem struct {
|
||||
CachedBytes int64
|
||||
CountRequests int64
|
||||
CountCachedRequests int64
|
||||
CountAttackRequests int64
|
||||
AttackBytes int64
|
||||
}
|
||||
|
||||
// TrafficStatManager 区域流量统计
|
||||
type TrafficStatManager struct {
|
||||
itemMap map[string]*TrafficItem // [timestamp serverId] => bytes
|
||||
itemMap map[string]*TrafficItem // [timestamp serverId] => *TrafficItem
|
||||
domainsMap map[string]*TrafficItem // timestamp @ serverId @ domain => *TrafficItem
|
||||
locker sync.Mutex
|
||||
configFunc func() *nodeconfigs.NodeConfig
|
||||
|
||||
totalRequests int64
|
||||
}
|
||||
|
||||
// NewTrafficStatManager 获取新对象
|
||||
func NewTrafficStatManager() *TrafficStatManager {
|
||||
manager := &TrafficStatManager{
|
||||
itemMap: map[string]*TrafficItem{},
|
||||
itemMap: map[string]*TrafficItem{},
|
||||
domainsMap: map[string]*TrafficItem{},
|
||||
}
|
||||
|
||||
return manager
|
||||
@@ -42,6 +52,20 @@ func NewTrafficStatManager() *TrafficStatManager {
|
||||
func (this *TrafficStatManager) Start(configFunc func() *nodeconfigs.NodeConfig) {
|
||||
this.configFunc = configFunc
|
||||
|
||||
// 上传请求总数
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
if this.totalRequests > 0 {
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemRequests, maps.Map{"total": this.totalRequests})
|
||||
this.totalRequests = 0
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
// 上传统计数据
|
||||
duration := 5 * time.Minute
|
||||
if Tea.IsTesting() {
|
||||
// 测试环境缩短上传时间,方便我们调试
|
||||
@@ -62,15 +86,19 @@ func (this *TrafficStatManager) Start(configFunc func() *nodeconfigs.NodeConfig)
|
||||
}
|
||||
|
||||
// Add 添加流量
|
||||
func (this *TrafficStatManager) Add(serverId int64, bytes int64, cachedBytes int64, countRequests int64, countCachedRequests int64) {
|
||||
func (this *TrafficStatManager) Add(serverId int64, domain string, bytes int64, cachedBytes int64, countRequests int64, countCachedRequests int64, countAttacks int64, attackBytes int64) {
|
||||
if bytes == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
this.totalRequests++
|
||||
|
||||
timestamp := utils.UnixTime() / 300 * 300
|
||||
|
||||
key := strconv.FormatInt(timestamp, 10) + strconv.FormatInt(serverId, 10)
|
||||
this.locker.Lock()
|
||||
|
||||
// 总的流量
|
||||
item, ok := this.itemMap[key]
|
||||
if !ok {
|
||||
item = &TrafficItem{}
|
||||
@@ -80,6 +108,23 @@ func (this *TrafficStatManager) Add(serverId int64, bytes int64, cachedBytes int
|
||||
item.CachedBytes += cachedBytes
|
||||
item.CountRequests += countRequests
|
||||
item.CountCachedRequests += countCachedRequests
|
||||
item.CountAttackRequests += countAttacks
|
||||
item.AttackBytes += attackBytes
|
||||
|
||||
// 单个域名流量
|
||||
var domainKey = strconv.FormatInt(timestamp, 10) + "@" + strconv.FormatInt(serverId, 10) + "@" + domain
|
||||
domainItem, ok := this.domainsMap[domainKey]
|
||||
if !ok {
|
||||
domainItem = &TrafficItem{}
|
||||
this.domainsMap[domainKey] = domainItem
|
||||
}
|
||||
domainItem.Bytes += bytes
|
||||
domainItem.CachedBytes += cachedBytes
|
||||
domainItem.CountRequests += countRequests
|
||||
domainItem.CountCachedRequests += countCachedRequests
|
||||
domainItem.CountAttackRequests += countAttacks
|
||||
domainItem.AttackBytes += attackBytes
|
||||
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
@@ -96,12 +141,15 @@ func (this *TrafficStatManager) Upload() error {
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
m := this.itemMap
|
||||
itemMap := this.itemMap
|
||||
domainMap := this.domainsMap
|
||||
this.itemMap = map[string]*TrafficItem{}
|
||||
this.domainsMap = map[string]*TrafficItem{}
|
||||
this.locker.Unlock()
|
||||
|
||||
pbStats := []*pb.ServerDailyStat{}
|
||||
for key, item := range m {
|
||||
// 服务统计
|
||||
var pbServerStats = []*pb.ServerDailyStat{}
|
||||
for key, item := range itemMap {
|
||||
timestamp, err := strconv.ParseInt(key[:10], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,19 +159,45 @@ func (this *TrafficStatManager) Upload() error {
|
||||
return err
|
||||
}
|
||||
|
||||
pbStats = append(pbStats, &pb.ServerDailyStat{
|
||||
pbServerStats = append(pbServerStats, &pb.ServerDailyStat{
|
||||
ServerId: serverId,
|
||||
RegionId: config.RegionId,
|
||||
Bytes: item.Bytes,
|
||||
CachedBytes: item.CachedBytes,
|
||||
CountRequests: item.CountRequests,
|
||||
CountCachedRequests: item.CountCachedRequests,
|
||||
CountAttackRequests: item.CountAttackRequests,
|
||||
AttackBytes: item.AttackBytes,
|
||||
CreatedAt: timestamp,
|
||||
})
|
||||
}
|
||||
if len(pbStats) == 0 {
|
||||
if len(pbServerStats) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err = client.ServerDailyStatRPC().UploadServerDailyStats(client.Context(), &pb.UploadServerDailyStatsRequest{Stats: pbStats})
|
||||
|
||||
// 域名统计
|
||||
var pbDomainStats = []*pb.UploadServerDailyStatsRequest_DomainStat{}
|
||||
for key, item := range domainMap {
|
||||
var pieces = strings.SplitN(key, "@", 3)
|
||||
if len(pieces) != 3 {
|
||||
continue
|
||||
}
|
||||
pbDomainStats = append(pbDomainStats, &pb.UploadServerDailyStatsRequest_DomainStat{
|
||||
ServerId: types.Int64(pieces[1]),
|
||||
Domain: pieces[2],
|
||||
Bytes: item.Bytes,
|
||||
CachedBytes: item.CachedBytes,
|
||||
CountRequests: item.CountRequests,
|
||||
CountCachedRequests: item.CountCachedRequests,
|
||||
CountAttackRequests: item.CountAttackRequests,
|
||||
AttackBytes: item.AttackBytes,
|
||||
CreatedAt: types.Int64(pieces[0]),
|
||||
})
|
||||
}
|
||||
|
||||
_, err = client.ServerDailyStatRPC().UploadServerDailyStats(client.Context(), &pb.UploadServerDailyStatsRequest{
|
||||
Stats: pbServerStats,
|
||||
DomainStats: pbDomainStats,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
func TestTrafficStatManager_Add(t *testing.T) {
|
||||
manager := NewTrafficStatManager()
|
||||
for i := 0; i < 100; i++ {
|
||||
manager.Add(1, 10, 1, 0)
|
||||
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0)
|
||||
}
|
||||
t.Log(manager.itemMap)
|
||||
}
|
||||
@@ -16,7 +16,7 @@ func TestTrafficStatManager_Add(t *testing.T) {
|
||||
func TestTrafficStatManager_Upload(t *testing.T) {
|
||||
manager := NewTrafficStatManager()
|
||||
for i := 0; i < 100; i++ {
|
||||
manager.Add(1, 10, 1, 0)
|
||||
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0)
|
||||
}
|
||||
err := manager.Upload()
|
||||
if err != nil {
|
||||
@@ -30,6 +30,6 @@ func BenchmarkTrafficStatManager_Add(b *testing.B) {
|
||||
|
||||
manager := NewTrafficStatManager()
|
||||
for i := 0; i < b.N; i++ {
|
||||
manager.Add(1, 1024, 1, 0)
|
||||
manager.Add(1, "goedge.cn", 1024, 1, 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TTL缓存
|
||||
// Cache TTL缓存
|
||||
// 最大的缓存时间为30 * 86400
|
||||
// Piece数据结构:
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
@@ -136,6 +136,12 @@ func (this *Cache) GC() {
|
||||
this.gcPieceIndex = newIndex
|
||||
}
|
||||
|
||||
func (this *Cache) Clean() {
|
||||
for _, piece := range this.pieces {
|
||||
piece.Clean()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Cache) Destroy() {
|
||||
this.isDestroyed = true
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ func NewPiece(maxItems int) *Piece {
|
||||
return &Piece{m: map[uint64]*Item{}, maxItems: maxItems}
|
||||
}
|
||||
|
||||
func (this *Piece) Add(key uint64, item *Item) () {
|
||||
func (this *Piece) Add(key uint64, item *Item) {
|
||||
this.locker.Lock()
|
||||
if len(this.m) >= this.maxItems {
|
||||
this.locker.Unlock()
|
||||
@@ -30,7 +30,7 @@ func (this *Piece) Add(key uint64, item *Item) () {
|
||||
func (this *Piece) IncreaseInt64(key uint64, delta int64, expiredAt int64) (result int64) {
|
||||
this.locker.Lock()
|
||||
item, ok := this.m[key]
|
||||
if ok {
|
||||
if ok && item.expiredAt > time.Now().Unix() {
|
||||
result = types.Int64(item.Value) + delta
|
||||
item.Value = result
|
||||
item.expiredAt = expiredAt
|
||||
@@ -82,6 +82,12 @@ func (this *Piece) GC() {
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Piece) Clean() {
|
||||
this.locker.Lock()
|
||||
this.m = map[uint64]*Item{}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Piece) Destroy() {
|
||||
this.locker.Lock()
|
||||
this.m = nil
|
||||
|
||||
159
internal/utils/encrypt.go
Normal file
159
internal/utils/encrypt.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
)
|
||||
|
||||
var (
|
||||
simpleEncryptMagicKey = rands.HexString(32)
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventReload, func() {
|
||||
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||
if nodeConfig != nil {
|
||||
simpleEncryptMagicKey = stringutil.Md5(nodeConfig.NodeId + "@" + nodeConfig.Secret)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SimpleEncrypt 加密特殊信息
|
||||
func SimpleEncrypt(data []byte) []byte {
|
||||
var method = &AES256CFBMethod{}
|
||||
err := method.Init([]byte(simpleEncryptMagicKey), []byte(simpleEncryptMagicKey[:16]))
|
||||
if err != nil {
|
||||
logs.Println("[SimpleEncrypt]" + err.Error())
|
||||
return data
|
||||
}
|
||||
|
||||
dst, err := method.Encrypt(data)
|
||||
if err != nil {
|
||||
logs.Println("[SimpleEncrypt]" + err.Error())
|
||||
return data
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// SimpleDecrypt 解密特殊信息
|
||||
func SimpleDecrypt(data []byte) []byte {
|
||||
var method = &AES256CFBMethod{}
|
||||
err := method.Init([]byte(simpleEncryptMagicKey), []byte(simpleEncryptMagicKey[:16]))
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
|
||||
src, err := method.Decrypt(data)
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func SimpleEncryptMap(m maps.Map) (base64String string, err error) {
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data := SimpleEncrypt(mJSON)
|
||||
return base64.StdEncoding.EncodeToString(data), nil
|
||||
}
|
||||
|
||||
func SimpleDecryptMap(base64String string) (maps.Map, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(base64String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mJSON := SimpleDecrypt(data)
|
||||
var result = maps.Map{}
|
||||
err = json.Unmarshal(mJSON, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type AES256CFBMethod struct {
|
||||
block cipher.Block
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为32长度
|
||||
l := len(key)
|
||||
if l > 32 {
|
||||
key = key[:32]
|
||||
} else if l < 32 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 32-l)...)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
this.iv = iv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
err = errors.New("encrypt failed")
|
||||
}
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
err = errors.New("decrypt failed")
|
||||
}
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
decrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
52
internal/utils/encrypt_test.go
Normal file
52
internal/utils/encrypt_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleEncrypt(t *testing.T) {
|
||||
var arr = []string{"Hello", "World", "People"}
|
||||
for _, s := range arr {
|
||||
var value = []byte(s)
|
||||
encoded := SimpleEncrypt(value)
|
||||
t.Log(encoded, string(encoded))
|
||||
decoded := SimpleDecrypt(encoded)
|
||||
t.Log(decoded, string(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleEncrypt_Concurrent(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
var arr = []string{"Hello", "World", "People"}
|
||||
wg.Add(len(arr))
|
||||
for _, s := range arr {
|
||||
go func(s string) {
|
||||
defer wg.Done()
|
||||
t.Log(string(SimpleDecrypt(SimpleEncrypt([]byte(s)))))
|
||||
}(s)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSimpleEncryptMap(t *testing.T) {
|
||||
var m = maps.Map{
|
||||
"s": "Hello",
|
||||
"i": 20,
|
||||
"b": true,
|
||||
}
|
||||
encodedResult, err := SimpleEncryptMap(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("result:", encodedResult)
|
||||
|
||||
decodedResult, err := SimpleDecryptMap(encodedResult)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(decodedResult)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ type List struct {
|
||||
itemsMap map[int64]int64 // itemId => timestamp
|
||||
|
||||
locker sync.Mutex
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
func NewList() *List {
|
||||
@@ -21,10 +22,7 @@ func NewList() *List {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *List) Add(itemId int64, expiredAt int64) {
|
||||
if expiredAt <= time.Now().Unix() {
|
||||
return
|
||||
}
|
||||
func (this *List) Add(itemId int64, expiresAt int64) {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
@@ -34,17 +32,17 @@ func (this *List) Add(itemId int64, expiredAt int64) {
|
||||
this.removeItem(itemId)
|
||||
}
|
||||
|
||||
expireItemMap, ok := this.expireMap[expiredAt]
|
||||
expireItemMap, ok := this.expireMap[expiresAt]
|
||||
if ok {
|
||||
expireItemMap[itemId] = true
|
||||
} else {
|
||||
expireItemMap = ItemMap{
|
||||
itemId: true,
|
||||
}
|
||||
this.expireMap[expiredAt] = expireItemMap
|
||||
this.expireMap[expiresAt] = expireItemMap
|
||||
}
|
||||
|
||||
this.itemsMap[itemId] = expiredAt
|
||||
this.itemsMap[itemId] = expiresAt
|
||||
}
|
||||
|
||||
func (this *List) Remove(itemId int64) {
|
||||
@@ -64,21 +62,22 @@ func (this *List) GC(timestamp int64, callback func(itemId int64)) {
|
||||
}
|
||||
|
||||
func (this *List) StartGC(callback func(itemId int64)) {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
this.ticker = time.NewTicker(1 * time.Second)
|
||||
lastTimestamp := int64(0)
|
||||
for range ticker.C {
|
||||
for range this.ticker.C {
|
||||
timestamp := time.Now().Unix()
|
||||
if lastTimestamp == 0 {
|
||||
lastTimestamp = timestamp - 3600
|
||||
}
|
||||
|
||||
// 防止死循环
|
||||
if lastTimestamp > timestamp {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := lastTimestamp; i <= timestamp; i++ {
|
||||
this.GC(timestamp, callback)
|
||||
if timestamp >= lastTimestamp {
|
||||
for i := lastTimestamp; i <= timestamp; i++ {
|
||||
this.GC(i, callback)
|
||||
}
|
||||
} else {
|
||||
for i := timestamp; i <= lastTimestamp; i++ {
|
||||
this.GC(i, callback)
|
||||
}
|
||||
}
|
||||
|
||||
// 这样做是为了防止系统时钟突变
|
||||
|
||||
@@ -58,6 +58,10 @@ func TestList_Start_GC(t *testing.T) {
|
||||
list.Add(2, time.Now().Unix()+1)
|
||||
list.Add(3, time.Now().Unix()+2)
|
||||
list.Add(4, time.Now().Unix()+5)
|
||||
list.Add(5, time.Now().Unix()+5)
|
||||
list.Add(6, time.Now().Unix()+6)
|
||||
list.Add(7, time.Now().Unix()+6)
|
||||
list.Add(8, time.Now().Unix()+6)
|
||||
|
||||
go func() {
|
||||
list.StartGC(func(itemId int64) {
|
||||
@@ -66,7 +70,7 @@ func TestList_Start_GC(t *testing.T) {
|
||||
})
|
||||
}()
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
time.Sleep(20 * time.Second)
|
||||
}
|
||||
|
||||
func TestList_ManyItems(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,9 @@ var timeoutClientLocker = sync.Mutex{}
|
||||
// 导出响应
|
||||
func DumpResponse(resp *http.Response) (header []byte, body []byte, err error) {
|
||||
header, err = httputil.DumpResponse(resp, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 将IP转换为整型
|
||||
// IP2Long 将IP转换为整型
|
||||
// 注意IPv6没有顺序
|
||||
func IP2Long(ip string) uint64 {
|
||||
if len(ip) == 0 {
|
||||
|
||||
35
internal/utils/jsonutils/map.go
Normal file
35
internal/utils/jsonutils/map.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package jsonutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
func MapToObject(m maps.Map, ptr interface{}) error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(mJSON, ptr)
|
||||
}
|
||||
|
||||
func ObjectToMap(ptr interface{}) (maps.Map, error) {
|
||||
if ptr == nil {
|
||||
return maps.Map{}, nil
|
||||
}
|
||||
ptrJSON, err := json.Marshal(ptr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result = maps.Map{}
|
||||
err = json.Unmarshal(ptrJSON, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
46
internal/utils/jsonutils/map_test.go
Normal file
46
internal/utils/jsonutils/map_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package jsonutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMapToObject(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
type typeA struct {
|
||||
B int `json:"b"`
|
||||
C bool `json:"c"`
|
||||
}
|
||||
|
||||
{
|
||||
var obj = &typeA{B: 1, C: true}
|
||||
m, err := ObjectToMap(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
PrintT(m, t)
|
||||
a.IsTrue(m.GetInt("b") == 1)
|
||||
a.IsTrue(m.GetBool("c") == true)
|
||||
}
|
||||
|
||||
{
|
||||
var obj = &typeA{}
|
||||
err := MapToObject(maps.Map{
|
||||
"b": 1024,
|
||||
"c": true,
|
||||
}, obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obj == nil {
|
||||
t.Fatal("obj should not be nil")
|
||||
}
|
||||
a.IsTrue(obj.B == 1024)
|
||||
a.IsTrue(obj.C == true)
|
||||
PrintT(obj, t)
|
||||
}
|
||||
}
|
||||
17
internal/utils/jsonutils/utils.go
Normal file
17
internal/utils/jsonutils/utils.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package jsonutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func PrintT(obj interface{}, t *testing.T) {
|
||||
data, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Log(string(data))
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,23 @@ import (
|
||||
type AllowAction struct {
|
||||
}
|
||||
|
||||
func (this *AllowAction) Perform(waf *WAF, request *requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
func (this *AllowAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AllowAction) Code() string {
|
||||
return ActionAllow
|
||||
}
|
||||
|
||||
func (this *AllowAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *AllowAction) WillChange() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *AllowAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
// do nothing
|
||||
return true
|
||||
}
|
||||
|
||||
21
internal/waf/action_base.go
Normal file
21
internal/waf/action_base.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import "net/http"
|
||||
|
||||
type BaseAction struct {
|
||||
}
|
||||
|
||||
// CloseConn 关闭连接
|
||||
func (this *BaseAction) CloseConn(writer http.ResponseWriter) error {
|
||||
// 断开连接
|
||||
hijack, ok := writer.(http.Hijacker)
|
||||
if ok {
|
||||
conn, _, err := hijack.Hijack()
|
||||
if err == nil {
|
||||
return conn.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -23,12 +23,48 @@ type BlockAction struct {
|
||||
StatusCode int `yaml:"statusCode" json:"statusCode"`
|
||||
Body string `yaml:"body" json:"body"` // supports HTML
|
||||
URL string `yaml:"url" json:"url"`
|
||||
Timeout int32 `yaml:"timeout" json:"timeout"`
|
||||
}
|
||||
|
||||
func (this *BlockAction) Perform(waf *WAF, request *requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
func (this *BlockAction) Init(waf *WAF) error {
|
||||
if waf.DefaultBlockAction != nil {
|
||||
if this.StatusCode <= 0 {
|
||||
this.StatusCode = waf.DefaultBlockAction.StatusCode
|
||||
}
|
||||
if len(this.Body) == 0 {
|
||||
this.Body = waf.DefaultBlockAction.Body
|
||||
}
|
||||
if len(this.URL) == 0 {
|
||||
this.URL = waf.DefaultBlockAction.URL
|
||||
}
|
||||
if this.Timeout <= 0 {
|
||||
this.Timeout = waf.DefaultBlockAction.Timeout
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BlockAction) Code() string {
|
||||
return ActionBlock
|
||||
}
|
||||
|
||||
func (this *BlockAction) IsAttack() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *BlockAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *BlockAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
if this.Timeout > 0 {
|
||||
// 加入到黑名单
|
||||
SharedIPBlackLIst.Add(IPTypeAll, request.WAFRemoteIP(), time.Now().Unix()+int64(this.Timeout))
|
||||
}
|
||||
|
||||
if writer != nil {
|
||||
// if status code eq 444, we close the connection
|
||||
if this.StatusCode == 444 {
|
||||
// close the connection
|
||||
defer func() {
|
||||
hijack, ok := writer.(http.Hijacker)
|
||||
if ok {
|
||||
conn, _, _ := hijack.Hijack()
|
||||
@@ -37,7 +73,7 @@ func (this *BlockAction) Perform(waf *WAF, request *requests.Request, writer htt
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// output response
|
||||
if this.StatusCode > 0 {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -13,27 +16,63 @@ var captchaSalt = stringutil.Rand(32)
|
||||
|
||||
const (
|
||||
CaptchaSeconds = 600 // 10 minutes
|
||||
CaptchaPath = "/WAF/VERIFY/CAPTCHA"
|
||||
)
|
||||
|
||||
type CaptchaAction struct {
|
||||
Life int32 `yaml:"life" json:"life"`
|
||||
Language string `yaml:"language" json:"language"` // 语言,zh-CN, en-US ...
|
||||
AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) Perform(waf *WAF, request *requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
// TEAWEB_CAPTCHA:
|
||||
cookie, err := request.Cookie("TEAWEB_WAF_CAPTCHA")
|
||||
if err == nil && cookie != nil && len(cookie.Value) > 32 {
|
||||
m := cookie.Value[:32]
|
||||
timestamp := cookie.Value[32:]
|
||||
if stringutil.Md5(captchaSalt+timestamp) == m && time.Now().Unix() < types.Int64(timestamp) { // verify md5
|
||||
return true
|
||||
func (this *CaptchaAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) Code() string {
|
||||
return ActionCaptcha
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
// 是否在白名单中
|
||||
if SharedIPWhiteList.Contains("set:"+set.Id, request.WAFRemoteIP()) {
|
||||
return true
|
||||
}
|
||||
|
||||
refURL := request.WAFRaw().URL.String()
|
||||
|
||||
// 覆盖配置
|
||||
if strings.HasPrefix(refURL, CaptchaPath) {
|
||||
info := request.WAFRaw().URL.Query().Get("info")
|
||||
if len(info) > 0 {
|
||||
m, err := utils.SimpleDecryptMap(info)
|
||||
if err == nil && m != nil {
|
||||
refURL = m.GetString("url")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refURL := request.URL.String()
|
||||
if len(request.Referer()) > 0 {
|
||||
refURL = request.Referer()
|
||||
var captchaConfig = maps.Map{
|
||||
"action": this,
|
||||
"timestamp": time.Now().Unix(),
|
||||
"url": refURL,
|
||||
"setId": set.Id,
|
||||
}
|
||||
http.Redirect(writer, request.Raw(), "/WAFCAPTCHA?url="+url.QueryEscape(refURL), http.StatusTemporaryRedirect)
|
||||
info, err := utils.SimpleEncryptMap(captchaConfig)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_CAPTCHA_ACTION", "encode captcha config failed: "+err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
http.Redirect(writer, request.WAFRaw(), CaptchaPath+"?info="+url.QueryEscape(info), http.StatusTemporaryRedirect)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
13
internal/waf/action_category.go
Normal file
13
internal/waf/action_category.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
|
||||
type ActionCategory = string
|
||||
|
||||
const (
|
||||
ActionCategoryAllow ActionCategory = firewallconfigs.HTTPFirewallActionCategoryAllow
|
||||
ActionCategoryBlock ActionCategory = firewallconfigs.HTTPFirewallActionCategoryBlock
|
||||
ActionCategoryVerify ActionCategory = firewallconfigs.HTTPFirewallActionCategoryVerify
|
||||
)
|
||||
10
internal/waf/action_config.go
Normal file
10
internal/waf/action_config.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import "github.com/iwind/TeaGo/maps"
|
||||
|
||||
type ActionConfig struct {
|
||||
Code string `yaml:"code" json:"code"`
|
||||
Options maps.Map `yaml:"options" json:"options"`
|
||||
}
|
||||
@@ -2,11 +2,12 @@ package waf
|
||||
|
||||
import "reflect"
|
||||
|
||||
// action definition
|
||||
// ActionDefinition action definition
|
||||
type ActionDefinition struct {
|
||||
Name string
|
||||
Code ActionString
|
||||
Description string
|
||||
Category string // category: block, verify, allow
|
||||
Instance ActionInterface
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
73
internal/waf/action_get_302.go
Normal file
73
internal/waf/action_get_302.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Get302Path = "/WAF/VERIFY/GET"
|
||||
)
|
||||
|
||||
// Get302Action
|
||||
// 原理: origin url --> 302 verify url --> origin url
|
||||
// TODO 将来支持meta refresh验证
|
||||
type Get302Action struct {
|
||||
BaseAction
|
||||
|
||||
Life int32 `yaml:"life" json:"life"`
|
||||
}
|
||||
|
||||
func (this *Get302Action) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Get302Action) Code() string {
|
||||
return ActionGet302
|
||||
}
|
||||
|
||||
func (this *Get302Action) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Get302Action) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *Get302Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
// 仅限于Get
|
||||
if request.WAFRaw().Method != http.MethodGet {
|
||||
return true
|
||||
}
|
||||
|
||||
// 是否已经在白名单中
|
||||
if SharedIPWhiteList.Contains("set:"+set.Id, request.WAFRemoteIP()) {
|
||||
return true
|
||||
}
|
||||
|
||||
var m = maps.Map{
|
||||
"url": request.WAFRaw().URL.String(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
"life": this.Life,
|
||||
"setId": set.Id,
|
||||
}
|
||||
info, err := utils.SimpleEncryptMap(m)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_GET_302_ACTION", "encode info failed: "+err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
http.Redirect(writer, request.WAFRaw(), Get302Path+"?info="+url.QueryEscape(info), http.StatusFound)
|
||||
|
||||
// 关闭连接
|
||||
if request.WAFRaw().ProtoMajor == 1 {
|
||||
_ = this.CloseConn(writer)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -10,13 +10,29 @@ type GoGroupAction struct {
|
||||
GroupId string `yaml:"groupId" json:"groupId"`
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) Perform(waf *WAF, request *requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
group := waf.FindRuleGroup(this.GroupId)
|
||||
if group == nil || !group.IsOn {
|
||||
func (this *GoGroupAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) Code() string {
|
||||
return ActionGoGroup
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
nextGroup := waf.FindRuleGroup(this.GroupId)
|
||||
if nextGroup == nil || !nextGroup.IsOn {
|
||||
return true
|
||||
}
|
||||
|
||||
b, set, err := group.MatchRequest(request)
|
||||
b, nextSet, err := nextGroup.MatchRequest(request)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return true
|
||||
@@ -26,9 +42,5 @@ func (this *GoGroupAction) Perform(waf *WAF, request *requests.Request, writer h
|
||||
return true
|
||||
}
|
||||
|
||||
actionObject := FindActionInstance(set.Action, set.ActionOptions)
|
||||
if actionObject == nil {
|
||||
return true
|
||||
}
|
||||
return actionObject.Perform(waf, request, writer)
|
||||
return nextSet.PerformActions(waf, nextGroup, request, writer)
|
||||
}
|
||||
|
||||
@@ -11,17 +11,33 @@ type GoSetAction struct {
|
||||
SetId string `yaml:"setId" json:"setId"`
|
||||
}
|
||||
|
||||
func (this *GoSetAction) Perform(waf *WAF, request *requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
group := waf.FindRuleGroup(this.GroupId)
|
||||
if group == nil || !group.IsOn {
|
||||
func (this *GoSetAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *GoSetAction) Code() string {
|
||||
return ActionGoSet
|
||||
}
|
||||
|
||||
func (this *GoSetAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *GoSetAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *GoSetAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||
nextGroup := waf.FindRuleGroup(this.GroupId)
|
||||
if nextGroup == nil || !nextGroup.IsOn {
|
||||
return true
|
||||
}
|
||||
set := group.FindRuleSet(this.SetId)
|
||||
if set == nil || !set.IsOn {
|
||||
nextSet := nextGroup.FindRuleSet(this.SetId)
|
||||
if nextSet == nil || !nextSet.IsOn {
|
||||
return true
|
||||
}
|
||||
|
||||
b, err := set.MatchRequest(request)
|
||||
b, err := nextSet.MatchRequest(request)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return true
|
||||
@@ -29,9 +45,5 @@ func (this *GoSetAction) Perform(waf *WAF, request *requests.Request, writer htt
|
||||
if !b {
|
||||
return true
|
||||
}
|
||||
actionObject := FindActionInstance(set.Action, set.ActionOptions)
|
||||
if actionObject == nil {
|
||||
return true
|
||||
}
|
||||
return actionObject.Perform(waf, request, writer)
|
||||
return nextSet.PerformActions(waf, nextGroup, request, writer)
|
||||
}
|
||||
|
||||
25
internal/waf/action_interface.go
Normal file
25
internal/waf/action_interface.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ActionInterface interface {
|
||||
// Init 初始化
|
||||
Init(waf *WAF) error
|
||||
|
||||
// Code 代号
|
||||
Code() string
|
||||
|
||||
// IsAttack 是否为拦截攻击动作
|
||||
IsAttack() bool
|
||||
|
||||
// WillChange determine if the action will change the request
|
||||
WillChange() bool
|
||||
|
||||
// Perform perform the action
|
||||
Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user