diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index deb97ab1b..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,64 +0,0 @@ -version: '{build}' -image: 'Visual Studio 2019' -platform: x64 - -max_jobs: 1 - -environment: - CGO_ENABLED: 0 - GOPATH: c:\go - docker_username: - secure: em/TNLUXxG19O/HvbvfJuQ== - docker_password: - secure: Yo9FJJqihaNz5q8T4Jz8tQ== - -branches: - only: - - master - -install: - - ps: | - choco install -y mingw - docker version - go version - -build_script: - - ps: | - if ( $env:APPVEYOR_REPO_TAG -eq 'false' ) { - $version = $env:APPVEYOR_REPO_COMMIT - $buildDate = $env:APPVEYOR_REPO_COMMIT_TIMESTAMP - } else { - $version = $env:APPVEYOR_REPO_TAG_NAME - $buildDate = $env:APPVEYOR_REPO_COMMIT_TIMESTAMP - } - go build -a -o release/gorush.exe . - - docker pull microsoft/nanoserver:10.0.14393.1884 - docker build -f docker/Dockerfile.windows.amd64 -t appleboy/gorush:windows-amd64 . - -test_script: - - ps: | - docker run --rm appleboy/gorush:windows-amd64 --version - -deploy_script: - - ps: | - $ErrorActionPreference = 'Stop'; - if ( $env:APPVEYOR_PULL_REQUEST_NUMBER ) { - Write-Host Nothing to deploy. - } else { - echo $env:DOCKER_PASSWORD | docker login --username $env:DOCKER_USERNAME --password-stdin - if ( $env:APPVEYOR_REPO_TAG -eq 'true' ) { - $major,$minor,$patch = $env:APPVEYOR_REPO_TAG_NAME.split('.') - docker push appleboy/gorush:windows-amd64 - docker tag appleboy/gorush:windows-amd64 appleboy/gorush:$major.$minor.$patch-windows-amd64 - docker push appleboy/gorush:$major.$minor.$patch-windows-amd64 - docker tag appleboy/gorush:windows-amd64 appleboy/gorush:$major.$minor-windows-amd64 - docker push appleboy/gorush:$major.$minor-windows-amd64 - docker tag appleboy/gorush:windows-amd64 appleboy/gorush:$major-windows-amd64 - docker push appleboy/gorush:$major-windows-amd64 - } else { - if ( $env:APPVEYOR_REPO_BRANCH -eq 'master' ) { - docker push appleboy/gorush:windows-amd64 - } - } - } diff --git a/.drone.yml b/.drone.yml index 57b9f89bc..b0d245258 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,29 +7,11 @@ platform: arch: amd64 steps: -- name: vet - pull: always - image: golang:1.16 - commands: - - make vet - volumes: - - name: gopath - path: /go - - name: lint pull: always - image: golang:1.16 - commands: - - make lint - volumes: - - name: gopath - path: /go - -- name: misspell - pull: always - image: golang:1.16 + image: golangci/golangci-lint:v1.41.1 commands: - - make misspell-check + - golangci-lint run -v volumes: - name: gopath path: /go @@ -80,6 +62,11 @@ services: - name: redis image: redis +- name: nsq + image: nsqio/nsq + commands: + - /nsqd + volumes: - name: gopath temp: {} @@ -122,19 +109,6 @@ steps: commands: - ./release/linux/amd64/gorush --help -- name: dryrun - pull: always - image: plugins/docker:linux-amd64 - settings: - cache_from: appleboy/gorush - dockerfile: docker/Dockerfile.linux.amd64 - dry_run: true - repo: appleboy/gorush - tags: linux-amd64 - when: - event: - - pull_request - - name: publish pull: always image: plugins/docker:linux-amd64 @@ -201,19 +175,6 @@ steps: commands: - ./release/linux/arm64/gorush --help -- name: dryrun - pull: always - image: plugins/docker:linux-arm64 - settings: - cache_from: appleboy/gorush - dockerfile: docker/Dockerfile.linux.arm64 - dry_run: true - repo: appleboy/gorush - tags: linux-arm64 - when: - event: - - pull_request - - name: publish pull: always image: plugins/docker:linux-arm64 @@ -280,19 +241,6 @@ steps: commands: - ./release/linux/arm/gorush --help -- name: dryrun - pull: always - image: plugins/docker:linux-arm - settings: - cache_from: appleboy/gorush - dockerfile: docker/Dockerfile.linux.arm - dry_run: true - repo: appleboy/gorush - tags: linux-arm - when: - event: - - pull_request - - name: publish pull: always image: plugins/docker:linux-arm diff --git a/.gitignore b/.gitignore index 683a0e610..1fd9c9a88 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ custom release coverage.txt node_modules +config.yml diff --git a/Makefile b/Makefile index f10c03881..abed8186e 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,11 @@ DEPLOY_ACCOUNT := appleboy DEPLOY_IMAGE := $(EXECUTABLE) GOFMT ?= gofumpt -l -s -extra -TARGETS ?= linux darwin windows openbsd -ARCHS ?= amd64 386 +TARGETS ?= linux darwin windows +ARCHS ?= amd64 GOFILES := $(shell find . -name "*.go" -type f) TAGS ?= sqlite LDFLAGS ?= -X 'main.Version=$(VERSION)' -NODE_PROTOC_PLUGIN := $(shell which grpc_tools_node_protoc_plugin) ifneq ($(shell uname), Darwin) EXTLDFLAGS = -extldflags "-static" $(null) @@ -149,16 +148,14 @@ clean: find . -name *.db -delete -rm -rf release dist .cover -rpc/example/node/gorush_*_pb.js: rpc/proto/gorush.proto - @hash grpc_tools_node_protoc_plugin > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - npm install -g grpc-tools; \ - fi - protoc -I rpc/proto rpc/proto/gorush.proto --js_out=import_style=commonjs,binary:rpc/example/node/ --grpc_out=rpc/example/node/ --plugin=protoc-gen-grpc=$(NODE_PROTOC_PLUGIN) +generate_proto_js: + npm install grpc-tools + protoc -I rpc/proto rpc/proto/gorush.proto --js_out=import_style=commonjs,binary:rpc/example/node/ --grpc_out=rpc/example/node/ --plugin=protoc-gen-grpc="node_modules/.bin/grpc_tools_node_protoc_plugin" -rpc/proto/gorush.pb.go: rpc/proto/gorush.proto - protoc -I rpc/proto rpc/proto/gorush.proto --go_out=plugins=grpc:rpc/proto +generate_proto_go: + protoc -I rpc/proto rpc/proto/gorush.proto --go_out=rpc/proto --go-grpc_out=require_unimplemented_servers=false:rpc/proto -generate_proto: rpc/proto/gorush.pb.go rpc/example/node/gorush_*_pb.js +generate_proto: generate_proto_go generate_proto_js version: @echo $(VERSION) diff --git a/README.md b/README.md index a7661a6e6..6b3b9f2ca 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,10 @@ A push notification micro server using [Gin](https://github.com/gin-gonic/gin) f [![GoDoc](https://godoc.org/github.com/appleboy/gorush?status.svg)](https://godoc.org/github.com/appleboy/gorush) [![Build Status](https://cloud.drone.io/api/badges/appleboy/gorush/status.svg)](https://cloud.drone.io/appleboy/gorush) -[![Build status](https://ci.appveyor.com/api/projects/status/ka4hvplssp1q2s5u?svg=true)](https://ci.appveyor.com/project/appleboy/gorush-fp5dh) [![codecov](https://codecov.io/gh/appleboy/gorush/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/gorush) [![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/gorush)](https://goreportcard.com/report/github.com/appleboy/gorush) [![codebeat badge](https://codebeat.co/badges/0a4eff2d-c9ac-46ed-8fd7-b59942983390)](https://codebeat.co/projects/github-com-appleboy-gorush) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/c82e0ed283474c5686d705ce64d004f7)](https://www.codacy.com/app/appleboy/gorush?utm_source=github.com&utm_medium=referral&utm_content=appleboy/gorush&utm_campaign=Badge_Grade) [![Docker Pulls](https://img.shields.io/docker/pulls/appleboy/gorush.svg)](https://hub.docker.com/r/appleboy/gorush/) -[![microbadger](https://images.microbadger.com/badges/image/appleboy/gorush.svg)](https://microbadger.com/images/appleboy/gorush "Get your own image badge on microbadger.com") -[![Release](https://github-release-version.herokuapp.com/github/appleboy/gorush/release.svg?style=flat)](https://github.com/appleboy/gorush/releases/latest) [![Netlify Status](https://api.netlify.com/api/v1/badges/8ab14c9f-44fd-4d9a-8bba-f73f76d253b1/deploy-status)](https://app.netlify.com/sites/gorush/deploys) [![Financial Contributors on Open Collective](https://opencollective.com/gorush/all/badge.svg?label=financial+contributors)](https://opencollective.com/gorush) @@ -94,6 +90,7 @@ A push notification micro server using [Gin](https://github.com/gin-gonic/gin) f - Support send notification through [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) protocol, we use [gRPC](https://grpc.io/) as default framework. - Support running in Docker, [Kubernetes](https://kubernetes.io/) or [AWS Lambda](https://aws.amazon.com/lambda) ([Native Support in Golang](https://aws.amazon.com/blogs/compute/announcing-go-support-for-aws-lambda/)) - Support graceful shutdown that workers and queue have been sent to APNs/FCM before shutdown service. +- Support different Queue as backend like [NSQ](https://nsq.io/) or [NATS](https://nats.io/), defaut engine is local [Channel](https://tour.golang.org/concurrency/2). See the default [YAML config example](config/testdata/config.yml): @@ -145,11 +142,22 @@ android: max_retry: 0 # resend fail notification, default value zero is disabled huawei: - enabled: true + enabled: false appsecret: "YOUR_APP_SECRET" appid: "YOUR_APP_ID" max_retry: 0 # resend fail notification, default value zero is disabled +queue: + engine: "local" # support "local", "nsq" and "nats " default value is "local" + nsq: + addr: 127.0.0.1:4150 + topic: gorush + channel: gorush + nats: + addr: 127.0.0.1:4222 + subj: gorush + queue: gorush + ios: enabled: false key_path: "key.pem" @@ -173,7 +181,8 @@ log: stat: engine: "memory" # support memory, redis, boltdb, buntdb or leveldb redis: - addr: "localhost:6379" + cluster: false + addr: "localhost:6379" # if cluster is true, you may set this to "localhost:6379,localhost:6380,localhost:6381" password: "" db: 0 boltdb: @@ -216,19 +225,19 @@ go get -u -v github.com/appleboy/gorush On linux ```sh -wget https://github.com/appleboy/gorush/releases/download/v1.13.0/gorush-v1.13.0-linux-amd64 -O gorush +wget https://github.com/appleboy/gorush/releases/download/v1.14.0/gorush-v1.14.0-linux-amd64 -O gorush ``` On OS X ```sh -wget https://github.com/appleboy/gorush/releases/download/v1.13.0/gorush-v1.13.0-darwin-amd64 -O gorush +wget https://github.com/appleboy/gorush/releases/download/v1.14.0/gorush-v1.14.0-darwin-amd64 -O gorush ``` On Windows ```sh -wget https://github.com/appleboy/gorush/releases/download/v1.13.0/gorush-v1.13.0-windows-amd64.exe -O gorush.exe +wget https://github.com/appleboy/gorush/releases/download/v1.14.0/gorush-v1.14.0-windows-amd64.exe -O gorush.exe ``` On macOS, use Homebrew. @@ -294,7 +303,7 @@ Huawei Options: Common Options: --topic iOS or Android topic message -h, --help Show this message - -v, --version Show version + -V, --version Show version ``` ### Send Android notification @@ -1042,7 +1051,7 @@ func main() { Badge: 1, Category: "test", Sound: "test", - Priority: proto.Priority_High, + Priority: proto.NotificationRequest_HIGH, Alert: &proto.Alert{ Title: "Test Title", Body: "Test Alert Body", @@ -1062,10 +1071,13 @@ func main() { }, }) if err != nil { - log.Fatalf("could not greet: %v", err) + log.Println("could not greet: ", err) + } + + if r != nil { + log.Printf("Success: %t\n", r.Success) + log.Printf("Count: %d\n", r.Counts) } - log.Printf("Success: %t\n", r.Success) - log.Printf("Count: %d\n", r.Counts) } ``` @@ -1144,7 +1156,7 @@ func main() { Badge: 1, Category: "test", Sound: "test", - Priority: proto.Priority_High, + Priority: proto.NotificationRequest_HIGH, Alert: &proto.Alert{ Title: "Test Title", Body: "Test Alert Body", @@ -1164,10 +1176,13 @@ func main() { }, }) if err != nil { - log.Fatalf("could not greet: %v", err) + log.Println("could not greet: ", err) + } + + if r != nil { + log.Printf("Success: %t\n", r.Success) + log.Printf("Count: %d\n", r.Counts) } - log.Printf("Success: %t\n", r.Success) - log.Printf("Count: %d\n", r.Counts) } ``` diff --git a/config/config.go b/config/config.go index ce37a7c22..8cdb11ddf 100644 --- a/config/config.go +++ b/config/config.go @@ -57,11 +57,22 @@ android: max_retry: 0 # resend fail notification, default value zero is disabled huawei: - enabled: true + enabled: false appsecret: "YOUR_APP_SECRET" appid: "YOUR_APP_ID" max_retry: 0 # resend fail notification, default value zero is disabled +queue: + engine: "local" # support "local", "nsq", default value is "local" + nsq: + addr: 127.0.0.1:4150 + topic: gorush + channel: gorush + nats: + addr: 127.0.0.1:4222 + subj: gorush + queue: gorush + ios: enabled: false key_path: "" @@ -85,7 +96,8 @@ log: stat: engine: "memory" # support memory, redis, boltdb, buntdb or leveldb redis: - addr: "localhost:6379" + cluster: false + addr: "localhost:6379" # if cluster is true, you may set this to "localhost:6379,localhost:6380,localhost:6381" password: "" db: 0 boltdb: @@ -106,6 +118,7 @@ type ConfYaml struct { Android SectionAndroid `yaml:"android"` Huawei SectionHuawei `yaml:"huawei"` Ios SectionIos `yaml:"ios"` + Queue SectionQueue `yaml:"queue"` Log SectionLog `yaml:"log"` Stat SectionStat `yaml:"stat"` GRPC SectionGRPC `yaml:"grpc"` @@ -201,8 +214,30 @@ type SectionStat struct { BadgerDB SectionBadgerDB `yaml:"badgerdb"` } +// SectionQueue is sub section of config. +type SectionQueue struct { + Engine string `yaml:"engine"` + NSQ SectionNSQ `yaml:"nsq"` + NATS SectionNATS `yaml:"nats"` +} + +// SectionNSQ is sub section of config. +type SectionNSQ struct { + Addr string `yaml:"addr"` + Topic string `yaml:"topic"` + Channel string `yaml:"channel"` +} + +// SectionNATS is sub section of config. +type SectionNATS struct { + Addr string `yaml:"addr"` + Subj string `yaml:"subj"` + Queue string `yaml:"queue"` +} + // SectionRedis is sub section of config. type SectionRedis struct { + Cluster bool `yaml:"cluster"` Addr string `yaml:"addr"` Password string `yaml:"password"` DB int `yaml:"db"` @@ -243,16 +278,16 @@ type SectionGRPC struct { } // LoadConf load config from file and read in environment variables that match -func LoadConf(confPath string) (ConfYaml, error) { - var conf ConfYaml +func LoadConf(confPath ...string) (*ConfYaml, error) { + conf := &ConfYaml{} viper.SetConfigType("yaml") viper.AutomaticEnv() // read in environment variables that match viper.SetEnvPrefix("gorush") // will be uppercased automatically viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - if confPath != "" { - content, err := ioutil.ReadFile(confPath) + if len(confPath) > 0 && confPath[0] != "" { + content, err := ioutil.ReadFile(confPath[0]) if err != nil { return conf, err } @@ -341,8 +376,18 @@ func LoadConf(confPath string) (ConfYaml, error) { conf.Log.ErrorLevel = viper.GetString("log.error_level") conf.Log.HideToken = viper.GetBool("log.hide_token") + // Queue Engine + conf.Queue.Engine = viper.GetString("queue.engine") + conf.Queue.NSQ.Addr = viper.GetString("queue.nsq.addr") + conf.Queue.NSQ.Topic = viper.GetString("queue.nsq.topic") + conf.Queue.NSQ.Channel = viper.GetString("queue.nsq.channel") + conf.Queue.NATS.Addr = viper.GetString("queue.nats.addr") + conf.Queue.NATS.Subj = viper.GetString("queue.nats.subj") + conf.Queue.NATS.Queue = viper.GetString("queue.nats.queue") + // Stat Engine conf.Stat.Engine = viper.GetString("stat.engine") + conf.Stat.Redis.Cluster = viper.GetBool("stat.redis.cluster") conf.Stat.Redis.Addr = viper.GetString("stat.redis.addr") conf.Stat.Redis.Password = viper.GetString("stat.redis.password") conf.Stat.Redis.DB = viper.GetInt("stat.redis.db") diff --git a/config/config_test.go b/config/config_test.go index ef7e5d372..ec3df0311 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -19,13 +19,13 @@ func TestMissingFile(t *testing.T) { type ConfigTestSuite struct { suite.Suite - ConfGorushDefault ConfYaml - ConfGorush ConfYaml + ConfGorushDefault *ConfYaml + ConfGorush *ConfYaml } func (suite *ConfigTestSuite) SetupTest() { var err error - suite.ConfGorushDefault, err = LoadConf("") + suite.ConfGorushDefault, err = LoadConf() if err != nil { panic("failed to load default config.yml") } @@ -88,6 +88,16 @@ func (suite *ConfigTestSuite) TestValidateConfDefault() { assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.KeyID) assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.TeamID) + // queue + assert.Equal(suite.T(), "local", suite.ConfGorushDefault.Queue.Engine) + assert.Equal(suite.T(), "127.0.0.1:4150", suite.ConfGorushDefault.Queue.NSQ.Addr) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NSQ.Topic) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NSQ.Channel) + + assert.Equal(suite.T(), "127.0.0.1:4222", suite.ConfGorushDefault.Queue.NATS.Addr) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NATS.Subj) + assert.Equal(suite.T(), "gorush", suite.ConfGorushDefault.Queue.NATS.Queue) + // log assert.Equal(suite.T(), "string", suite.ConfGorushDefault.Log.Format) assert.Equal(suite.T(), "stdout", suite.ConfGorushDefault.Log.AccessLog) @@ -97,6 +107,7 @@ func (suite *ConfigTestSuite) TestValidateConfDefault() { assert.Equal(suite.T(), true, suite.ConfGorushDefault.Log.HideToken) assert.Equal(suite.T(), "memory", suite.ConfGorushDefault.Stat.Engine) + assert.Equal(suite.T(), false, suite.ConfGorushDefault.Stat.Redis.Cluster) assert.Equal(suite.T(), "localhost:6379", suite.ConfGorushDefault.Stat.Redis.Addr) assert.Equal(suite.T(), "", suite.ConfGorushDefault.Stat.Redis.Password) assert.Equal(suite.T(), 0, suite.ConfGorushDefault.Stat.Redis.DB) @@ -174,6 +185,7 @@ func (suite *ConfigTestSuite) TestValidateConf() { assert.Equal(suite.T(), true, suite.ConfGorush.Log.HideToken) assert.Equal(suite.T(), "memory", suite.ConfGorush.Stat.Engine) + assert.Equal(suite.T(), false, suite.ConfGorush.Stat.Redis.Cluster) assert.Equal(suite.T(), "localhost:6379", suite.ConfGorush.Stat.Redis.Addr) assert.Equal(suite.T(), "", suite.ConfGorush.Stat.Redis.Password) assert.Equal(suite.T(), 0, suite.ConfGorush.Stat.Redis.DB) @@ -215,6 +227,6 @@ func TestLoadConfigFromEnv(t *testing.T) { func TestLoadWrongDefaultYAMLConfig(t *testing.T) { defaultConf = []byte(`a`) - _, err := LoadConf("") + _, err := LoadConf() assert.Error(t, err) } diff --git a/config/testdata/config.yml b/config/testdata/config.yml index 34d786111..daa3b9385 100644 --- a/config/testdata/config.yml +++ b/config/testdata/config.yml @@ -44,11 +44,22 @@ android: max_retry: 0 # resend fail notification, default value zero is disabled huawei: - enabled: true + enabled: false appsecret: "YOUR_APP_SECRET" appid: "YOUR_APP_ID" max_retry: 0 # resend fail notification, default value zero is disabled +queue: + engine: "local" # support "local", "nsq" and "nats " default value is "local" + nsq: + addr: 127.0.0.1:4150 + topic: gorush + channel: gorush + nats: + addr: 127.0.0.1:4222 + subj: gorush + queue: gorush + ios: enabled: false key_path: "key.pem" @@ -72,7 +83,8 @@ log: stat: engine: "memory" # support memory, redis, boltdb, buntdb or leveldb redis: - addr: "localhost:6379" + cluster: false + addr: "localhost:6379" # if cluster is true, you may set this to "localhost:6379,localhost:6380,localhost:6381" password: "" db: 0 boltdb: diff --git a/core/core.go b/core/core.go new file mode 100644 index 000000000..4791bb025 --- /dev/null +++ b/core/core.go @@ -0,0 +1,17 @@ +package core + +const ( + // PlatFormIos constant is 1 for iOS + PlatFormIos = iota + 1 + // PlatFormAndroid constant is 2 for Android + PlatFormAndroid + // PlatFormHuawei constant is 3 for Huawei + PlatFormHuawei +) + +const ( + // SucceededPush is log block + SucceededPush = "succeeded-push" + // FailedPush is log block + FailedPush = "failed-push" +) diff --git a/core/queue.go b/core/queue.go new file mode 100644 index 000000000..2e1efab17 --- /dev/null +++ b/core/queue.go @@ -0,0 +1,18 @@ +package core + +// Queue as backend +type Queue string + +var ( + // LocalQueue for channel in Go + LocalQueue Queue = "local" + // NSQ a realtime distributed messaging platform + NSQ Queue = "nsq" + // NATS Connective Technology for Adaptive Edge & Distributed Systems + NATS Queue = "nats" +) + +// IsLocalQueue check is Local Queue +func IsLocalQueue(q Queue) bool { + return q == LocalQueue +} diff --git a/docker/Dockerfile.linux.amd64 b/docker/Dockerfile.linux.amd64 index 33be19615..784ea73e4 100644 --- a/docker/Dockerfile.linux.amd64 +++ b/docker/Dockerfile.linux.amd64 @@ -8,7 +8,7 @@ LABEL maintainer="Bo-Yi Wu " \ COPY release/linux/amd64/gorush /bin/ EXPOSE 8088 9000 -HEALTHCHECK --start-period=2s --interval=10s --timeout=5s \ +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ CMD ["/bin/gorush", "--ping"] ENTRYPOINT ["/bin/gorush"] diff --git a/docker/Dockerfile.linux.arm b/docker/Dockerfile.linux.arm index df81791f8..7e06c24c2 100644 --- a/docker/Dockerfile.linux.arm +++ b/docker/Dockerfile.linux.arm @@ -8,7 +8,7 @@ LABEL maintainer="Bo-Yi Wu " \ COPY release/linux/arm/gorush /bin/ EXPOSE 8088 9000 -HEALTHCHECK --start-period=2s --interval=10s --timeout=5s \ +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ CMD ["/bin/gorush", "--ping"] ENTRYPOINT ["/bin/gorush"] diff --git a/docker/Dockerfile.linux.arm64 b/docker/Dockerfile.linux.arm64 index 0372a26c4..cc4206298 100644 --- a/docker/Dockerfile.linux.arm64 +++ b/docker/Dockerfile.linux.arm64 @@ -8,7 +8,7 @@ LABEL maintainer="Bo-Yi Wu " \ COPY release/linux/arm64/gorush /bin/ EXPOSE 8088 9000 -HEALTHCHECK --start-period=2s --interval=10s --timeout=5s \ +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ CMD ["/bin/gorush", "--ping"] ENTRYPOINT ["/bin/gorush"] diff --git a/docker/Dockerfile.windows.amd64 b/docker/Dockerfile.windows.amd64 index 79f8f3be0..db2faa237 100644 --- a/docker/Dockerfile.windows.amd64 +++ b/docker/Dockerfile.windows.amd64 @@ -8,7 +8,7 @@ LABEL maintainer="Bo-Yi Wu " \ COPY release/gorush.exe C:/bin/gorush.exe EXPOSE 8088 9000 -HEALTHCHECK --start-period=2s --interval=10s --timeout=5s \ +HEALTHCHECK --start-period=1s --interval=10s --timeout=5s \ CMD ["\\gorush.exe", "--ping"] ENTRYPOINT [ "C:\\bin\\gorush.exe" ] diff --git a/go.mod b/go.mod index 6f3f320bb..9ed63a10c 100644 --- a/go.mod +++ b/go.mod @@ -9,16 +9,22 @@ require ( github.com/appleboy/gofight/v2 v2.1.2 github.com/asdine/storm/v3 v3.2.1 github.com/buger/jsonparser v1.1.1 - github.com/dgraph-io/badger/v2 v2.2007.2 - github.com/gin-contrib/logger v0.0.2 - github.com/gin-gonic/gin v1.6.3 - github.com/go-redis/redis/v7 v7.4.0 - github.com/golang/protobuf v1.5.1 + github.com/dgraph-io/badger/v3 v3.2103.1 + github.com/gin-contrib/logger v0.2.0 + github.com/gin-gonic/gin v1.7.4 + github.com/go-redis/redis/v8 v8.11.3 + github.com/golang-queue/nats v0.0.2 + github.com/golang-queue/nsq v0.0.2 + github.com/golang-queue/queue v0.0.7 + github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect + github.com/golang/protobuf v1.5.2 + github.com/google/flatbuffers v2.0.0+incompatible // indirect + github.com/json-iterator/go v1.1.10 github.com/mattn/go-isatty v0.0.12 github.com/mitchellh/mapstructure v1.4.1 - github.com/msalihkarakasli/go-hms-push v0.0.0-20200616114002-91cd23dfeed4 + github.com/msalihkarakasli/go-hms-push v0.0.0-20210731212030-00e7b986815b github.com/prometheus/client_golang v1.10.0 - github.com/rs/zerolog v1.21.0 + github.com/rs/zerolog v1.23.0 github.com/sideshow/apns2 v0.20.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/viper v1.7.1 @@ -26,9 +32,9 @@ require ( github.com/syndtr/goleveldb v1.0.0 github.com/thoas/stats v0.0.0-20190407194641-965cb2de1678 github.com/tidwall/buntdb v1.2.0 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 - golang.org/x/net v0.0.0-20210326220855-61e056675ecf + golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.36.1 - google.golang.org/protobuf v1.26.0 + google.golang.org/protobuf v1.27.1 ) diff --git a/go.sum b/go.sum index 8b722ff68..8365407c6 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -89,14 +90,16 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do 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/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= -github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= -github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= -github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/badger/v3 v3.2103.1 h1:zaX53IRg7ycxVlkd5pYdCeFp1FynD6qBGQoQql3R3Hk= +github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -113,20 +116,20 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fukata/golang-stats-api-handler v1.0.0 h1:N6M25vhs1yAvwGBpFY6oBmMOZeJdcWnvA+wej8pKeko= github.com/fukata/golang-stats-api-handler v1.0.0/go.mod h1:1sIi4/rHq6s/ednWMZqTmRq3765qTUSs/c3xF6lj8J8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/logger v0.0.2 h1:qT6qOR9/mp9hrHAgTEVxpjvS3anQtiUTtzJhf+NlBQM= -github.com/gin-contrib/logger v0.0.2/go.mod h1:Eca5g93bobBwWSNeuLdTqRvNK6btb3XSHdU9ePZ+toM= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/logger v0.2.0 h1:YkdOGKdm/Nnrrd3bjBjcjd3ow1kR2KUfxxP4/rlL23E= +github.com/gin-contrib/logger v0.2.0/go.mod h1:dYxbt3GB+rvPyJSvox5lLsnKYwh8PjWrC9TQtR+hpUw= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= +github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -142,19 +145,32 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= -github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8= +github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-queue/nats v0.0.2 h1:81Ege/02a9d2a1SDN2/t1S1XIr/JpY+s8xj/pLZIHXM= +github.com/golang-queue/nats v0.0.2/go.mod h1:dzXOwbx20CJ5oX4UiBIDyfedCiR5sBWWKJVXmiRlDYc= +github.com/golang-queue/nsq v0.0.2 h1:kP4fMLl1K6TNlJGq3tJ4t07e703mDGiMYLLltT8F4/Q= +github.com/golang-queue/nsq v0.0.2/go.mod h1:1Q/8y4BclWLj03sn0dApJIObatC3qMX5gLjlbo0TwXs= +github.com/golang-queue/queue v0.0.7 h1:WENCPyPBcIWYgBFSAZ8USGtwmxeCeMkhjwuyM0MAi84= +github.com/golang-queue/queue v0.0.7/go.mod h1:JS5tYJacahCjafcplU5idNLX2vkYioqh6wEDX5o9Nms= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -172,21 +188,26 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1 h1:jAbXjIeW2ZSW2AwFxlGTDoc2CjI2XujLkV3ArsZFCvc= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -232,7 +253,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -252,7 +272,11 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -273,7 +297,6 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -281,6 +304,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -297,28 +322,48 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/msalihkarakasli/go-hms-push v0.0.0-20200616114002-91cd23dfeed4 h1:3cAiZwpOmIXbP+3/SAriDi9XlxBwZCGhS0UGhnlYhi0= -github.com/msalihkarakasli/go-hms-push v0.0.0-20200616114002-91cd23dfeed4/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc= +github.com/msalihkarakasli/go-hms-push v0.0.0-20210731212030-00e7b986815b h1:GPoWsisltEZb9Itu1MMCXCUsYMKqC51nIG+3EqA3MdE= +github.com/msalihkarakasli/go-hms-push v0.0.0-20210731212030-00e7b986815b/go.mod h1:4X2lQHsWGt+e3uRK124A6ndq3IIVymTAzEI9A1kIQKc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats-server/v2 v2.3.4 h1:WcNa6HDFX8gjZPHb8CJ9wxRHEjJSlhWUb/MKb6/mlUY= +github.com/nats-io/nats-server/v2 v2.3.4/go.mod h1:3mtbaN5GkCo/Z5T3nNj0I0/W1fPkKzLiDC6jjWJKp98= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 h1:9GqilBhZaR3xYis0JgMlJjNw933WIobdjKhilXm+Vls= +github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nsqio/go-nsq v1.0.8 h1:3L2F8tNLlwXXlp2slDUrUWSBn2O3nMh8R1/KEDFTHPk= +github.com/nsqio/go-nsq v1.0.8/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= @@ -380,9 +425,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.16.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= -github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM= -github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= +github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= +github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -458,7 +502,6 @@ github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -471,8 +514,8 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= @@ -483,6 +526,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -499,9 +544,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -521,6 +568,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -540,13 +588,14 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326220855-61e056675ecf h1:WUcCxqQqDT0aXO4VnQbfMvp4zh7m1Gb2clVuHUAGGRE= -golang.org/x/net v0.0.0-20210326220855-61e056675ecf/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -569,8 +618,8 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -578,12 +627,13 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -593,21 +643,26 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -624,7 +679,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -632,6 +686,9 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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= @@ -687,8 +744,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -696,11 +754,9 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -714,8 +770,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gorush/const.go b/gorush/const.go deleted file mode 100644 index ac1a82c45..000000000 --- a/gorush/const.go +++ /dev/null @@ -1,28 +0,0 @@ -package gorush - -const ( - // PlatFormIos constant is 1 for iOS - PlatFormIos = iota + 1 - // PlatFormAndroid constant is 2 for Android - PlatFormAndroid - // PlatFormHuawei constant is 3 for Huawei - PlatFormHuawei -) - -const ( - // SucceededPush is log block - SucceededPush = "succeeded-push" - // FailedPush is log block - FailedPush = "failed-push" -) - -// Stat variable for redis -const ( - TotalCountKey = "gorush-total-count" - IosSuccessKey = "gorush-ios-success-count" - IosErrorKey = "gorush-ios-error-count" - AndroidSuccessKey = "gorush-android-success-count" - AndroidErrorKey = "gorush-android-error-count" - HuaweiSuccessKey = "gorush-huawei-success-count" - HuaweiErrorKey = "gorush-huawei-error-count" -) diff --git a/gorush/global.go b/gorush/global.go deleted file mode 100644 index 11a2d601e..000000000 --- a/gorush/global.go +++ /dev/null @@ -1,32 +0,0 @@ -package gorush - -import ( - "github.com/appleboy/gorush/config" - "github.com/appleboy/gorush/storage" - - "github.com/appleboy/go-fcm" - "github.com/msalihkarakasli/go-hms-push/push/core" - "github.com/sideshow/apns2" - "github.com/sirupsen/logrus" -) - -var ( - // PushConf is gorush config - PushConf config.ConfYaml - // QueueNotification is chan type - QueueNotification chan PushNotification - // ApnsClient is apns client - ApnsClient *apns2.Client - // FCMClient is apns client - FCMClient *fcm.Client - // HMSClient is Huawei push client - HMSClient *core.HMSClient - // LogAccess is log server request log - LogAccess *logrus.Logger - // LogError is log server error log - LogError *logrus.Logger - // StatStorage implements the storage interface - StatStorage storage.Storage - // MaxConcurrentIOSPushes pool to limit the number of concurrent iOS pushes - MaxConcurrentIOSPushes chan struct{} -) diff --git a/gorush/log_test.go b/gorush/log_test.go deleted file mode 100644 index be2867af6..000000000 --- a/gorush/log_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package gorush - -import ( - "testing" - - "github.com/appleboy/gorush/config" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" -) - -func TestSetLogLevel(t *testing.T) { - log := logrus.New() - - err := SetLogLevel(log, "debug") - assert.Nil(t, err) - - err = SetLogLevel(log, "invalid") - assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) -} - -func TestSetLogOut(t *testing.T) { - log := logrus.New() - - err := SetLogOut(log, "stdout") - assert.Nil(t, err) - - err = SetLogOut(log, "stderr") - assert.Nil(t, err) - - err = SetLogOut(log, "log/access.log") - assert.Nil(t, err) - - // missing create logs folder. - err = SetLogOut(log, "logs/access.log") - assert.NotNil(t, err) -} - -func TestInitDefaultLog(t *testing.T) { - PushConf, _ = config.LoadConf("") - - // no errors on default config - assert.Nil(t, InitLog()) - - PushConf.Log.AccessLevel = "invalid" - - assert.NotNil(t, InitLog()) -} - -func TestAccessLevel(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Log.AccessLevel = "invalid" - - assert.NotNil(t, InitLog()) -} - -func TestErrorLevel(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Log.ErrorLevel = "invalid" - - assert.NotNil(t, InitLog()) -} - -func TestAccessLogPath(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Log.AccessLog = "logs/access.log" - - assert.NotNil(t, InitLog()) -} - -func TestErrorLogPath(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Log.ErrorLog = "logs/error.log" - - assert.NotNil(t, InitLog()) -} - -func TestPlatFormType(t *testing.T) { - assert.Equal(t, "ios", typeForPlatForm(PlatFormIos)) - assert.Equal(t, "android", typeForPlatForm(PlatFormAndroid)) - assert.Equal(t, "", typeForPlatForm(10000)) -} - -func TestPlatFormColor(t *testing.T) { - assert.Equal(t, blue, colorForPlatForm(PlatFormIos)) - assert.Equal(t, yellow, colorForPlatForm(PlatFormAndroid)) - assert.Equal(t, reset, colorForPlatForm(1000000)) -} - -func TestHideToken(t *testing.T) { - assert.Equal(t, "", hideToken("", 2)) - assert.Equal(t, "**345678**", hideToken("1234567890", 2)) - assert.Equal(t, "*****", hideToken("12345", 10)) -} diff --git a/gorush/notification_hms_test.go b/gorush/notification_hms_test.go deleted file mode 100644 index 81dcd349b..000000000 --- a/gorush/notification_hms_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package gorush - -import ( - "context" - "log" - "sync" - "testing" - - "github.com/appleboy/gorush/config" - "github.com/stretchr/testify/assert" -) - -func init() { - PushConf, _ = config.LoadConf("") - if err := InitLog(); err != nil { - log.Fatal(err) - } - - ctx := context.Background() - wg := &sync.WaitGroup{} - wg.Add(int(PushConf.Core.WorkerNum)) - InitWorkers(ctx, wg, PushConf.Core.WorkerNum, PushConf.Core.QueueNum) - - if err := InitAppStatus(); err != nil { - log.Fatal(err) - } -} - -func TestMissingHuaweiAppSecret(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Huawei.Enabled = true - PushConf.Huawei.AppSecret = "" - - err := CheckPushConf() - - assert.Error(t, err) - assert.Equal(t, "Missing Huawei App Secret", err.Error()) -} - -func TestMissingHuaweiAppID(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Huawei.Enabled = true - PushConf.Huawei.AppID = "" - - err := CheckPushConf() - - assert.Error(t, err) - assert.Equal(t, "Missing Huawei App ID", err.Error()) -} - -func TestMissingAppSecretForInitHMSClient(t *testing.T) { - client, err := InitHMSClient("", "APP_SECRET") - - assert.Nil(t, client) - assert.Error(t, err) - assert.Equal(t, "Missing Huawei App Secret", err.Error()) -} - -func TestMissingAppIDForInitHMSClient(t *testing.T) { - client, err := InitHMSClient("APP_ID", "") - - assert.Nil(t, client) - assert.Error(t, err) - assert.Equal(t, "Missing Huawei App ID", err.Error()) -} diff --git a/gorush/notification_test.go b/gorush/notification_test.go deleted file mode 100644 index ca0ff9c97..000000000 --- a/gorush/notification_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package gorush - -import ( - "context" - "os" - "testing" - - "github.com/appleboy/gorush/config" - "github.com/stretchr/testify/assert" -) - -func TestCorrectConf(t *testing.T) { - PushConf, _ = config.LoadConf("") - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = "xxxxx" - - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - - err := CheckPushConf() - - assert.NoError(t, err) -} - -func TestSenMultipleNotifications(t *testing.T) { - ctx := context.Background() - PushConf, _ = config.LoadConf("") - - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() - assert.Nil(t, err) - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - - androidToken := os.Getenv("ANDROID_TEST_TOKEN") - - req := RequestPush{ - Notifications: []PushNotification{ - // ios - { - Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, - Platform: PlatFormIos, - Message: "Welcome", - }, - // android - { - Tokens: []string{androidToken, "bbbbb"}, - Platform: PlatFormAndroid, - Message: "Welcome", - }, - }, - } - - count, logs := queueNotification(ctx, req) - assert.Equal(t, 3, count) - assert.Equal(t, 0, len(logs)) -} - -func TestDisabledAndroidNotifications(t *testing.T) { - ctx := context.Background() - PushConf, _ = config.LoadConf("") - - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() - assert.Nil(t, err) - - PushConf.Android.Enabled = false - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - - androidToken := os.Getenv("ANDROID_TEST_TOKEN") - - req := RequestPush{ - Notifications: []PushNotification{ - // ios - { - Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, - Platform: PlatFormIos, - Message: "Welcome", - }, - // android - { - Tokens: []string{androidToken, "bbbbb"}, - Platform: PlatFormAndroid, - Message: "Welcome", - }, - }, - } - - count, logs := queueNotification(ctx, req) - assert.Equal(t, 1, count) - assert.Equal(t, 0, len(logs)) -} - -func TestSyncModeForNotifications(t *testing.T) { - ctx := context.Background() - PushConf, _ = config.LoadConf("") - - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() - assert.Nil(t, err) - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - - // enable sync mode - PushConf.Core.Sync = true - - androidToken := os.Getenv("ANDROID_TEST_TOKEN") - - req := RequestPush{ - Notifications: []PushNotification{ - // ios - { - Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, - Platform: PlatFormIos, - Message: "Welcome", - }, - // android - { - Tokens: []string{androidToken, "bbbbb"}, - Platform: PlatFormAndroid, - Message: "Welcome", - }, - }, - } - - count, logs := queueNotification(ctx, req) - assert.Equal(t, 3, count) - assert.Equal(t, 2, len(logs)) -} - -func TestSyncModeForTopicNotification(t *testing.T) { - ctx := context.Background() - PushConf, _ = config.LoadConf("") - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - PushConf.Log.HideToken = false - - // enable sync mode - PushConf.Core.Sync = true - - req := RequestPush{ - Notifications: []PushNotification{ - // android - { - // error:InvalidParameters - // Check that the provided parameters have the right name and type. - To: "/topics/foo-bar@@@##", - Platform: PlatFormAndroid, - Message: "This is a Firebase Cloud Messaging Topic Message!", - }, - // android - { - // success - To: "/topics/foo-bar", - Platform: PlatFormAndroid, - Message: "This is a Firebase Cloud Messaging Topic Message!", - }, - // android - { - // success - Condition: "'dogs' in topics || 'cats' in topics", - Platform: PlatFormAndroid, - Message: "This is a Firebase Cloud Messaging Topic Message!", - }, - }, - } - - count, logs := queueNotification(ctx, req) - assert.Equal(t, 2, count) - assert.Equal(t, 1, len(logs)) -} - -func TestSyncModeForDeviceGroupNotification(t *testing.T) { - ctx := context.Background() - PushConf, _ = config.LoadConf("") - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - PushConf.Log.HideToken = false - - // enable sync mode - PushConf.Core.Sync = true - - req := RequestPush{ - Notifications: []PushNotification{ - // android - { - To: "aUniqueKey", - Platform: PlatFormAndroid, - Message: "This is a Firebase Cloud Messaging Device Group Message!", - }, - }, - } - - count, logs := queueNotification(ctx, req) - assert.Equal(t, 1, count) - assert.Equal(t, 1, len(logs)) -} - -func TestSetProxyURL(t *testing.T) { - err := SetProxy("87.236.233.92:8080") - assert.Error(t, err) - assert.Equal(t, "parse \"87.236.233.92:8080\": invalid URI for request", err.Error()) - - err = SetProxy("a.html") - assert.Error(t, err) - - err = SetProxy("http://87.236.233.92:8080") - assert.NoError(t, err) -} diff --git a/gorush/server.go b/gorush/server.go deleted file mode 100644 index 952f8e560..000000000 --- a/gorush/server.go +++ /dev/null @@ -1,167 +0,0 @@ -package gorush - -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - "os" - "regexp" - - api "github.com/appleboy/gin-status-api" - "github.com/gin-contrib/logger" - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "golang.org/x/crypto/acme/autocert" -) - -var rxURL = regexp.MustCompile(`^/healthz$`) - -func init() { - // Support metrics - m := NewMetrics() - prometheus.MustRegister(m) -} - -func abortWithError(c *gin.Context, code int, message string) { - c.AbortWithStatusJSON(code, gin.H{ - "code": code, - "message": message, - }) -} - -func rootHandler(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "text": "Welcome to notification server.", - }) -} - -func heartbeatHandler(c *gin.Context) { - c.AbortWithStatus(http.StatusOK) -} - -func versionHandler(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "source": "https://github.com/appleboy/gorush", - "version": GetVersion(), - }) -} - -func pushHandler(c *gin.Context) { - var form RequestPush - var msg string - - if err := c.ShouldBindWith(&form, binding.JSON); err != nil { - msg = "Missing notifications field." - LogAccess.Debug(err) - abortWithError(c, http.StatusBadRequest, msg) - return - } - - if len(form.Notifications) == 0 { - msg = "Notifications field is empty." - LogAccess.Debug(msg) - abortWithError(c, http.StatusBadRequest, msg) - return - } - - if int64(len(form.Notifications)) > PushConf.Core.MaxNotification { - msg = fmt.Sprintf("Number of notifications(%d) over limit(%d)", len(form.Notifications), PushConf.Core.MaxNotification) - LogAccess.Debug(msg) - abortWithError(c, http.StatusBadRequest, msg) - return - } - - ctx, cancel := context.WithCancel(context.Background()) - go func() { - // Deprecated: the CloseNotifier interface predates Go's context package. - // New code should use Request.Context instead. - // Change to context package - <-c.Request.Context().Done() - // Don't send notification after client timeout or disconnected. - // See the following issue for detail information. - // https://github.com/appleboy/gorush/issues/422 - if PushConf.Core.Sync { - cancel() - } - }() - - counts, logs := queueNotification(ctx, form) - - c.JSON(http.StatusOK, gin.H{ - "success": "ok", - "counts": counts, - "logs": logs, - }) -} - -func configHandler(c *gin.Context) { - c.YAML(http.StatusCreated, PushConf) -} - -func metricsHandler(c *gin.Context) { - promhttp.Handler().ServeHTTP(c.Writer, c.Request) -} - -func autoTLSServer() *http.Server { - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(PushConf.Core.AutoTLS.Host), - Cache: autocert.DirCache(PushConf.Core.AutoTLS.Folder), - } - - return &http.Server{ - Addr: ":https", - TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, - Handler: routerEngine(), - } -} - -func routerEngine() *gin.Engine { - zerolog.SetGlobalLevel(zerolog.InfoLevel) - if PushConf.Core.Mode == "debug" { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - } - - log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger() - - if isTerm { - log.Logger = log.Output( - zerolog.ConsoleWriter{ - Out: os.Stdout, - NoColor: false, - }, - ) - } - - // set server mode - gin.SetMode(PushConf.Core.Mode) - - r := gin.New() - - // Global middleware - r.Use(logger.SetLogger(logger.Config{ - UTC: true, - SkipPathRegexp: rxURL, - })) - r.Use(gin.Recovery()) - r.Use(VersionMiddleware()) - r.Use(StatMiddleware()) - - r.GET(PushConf.API.StatGoURI, api.GinHandler) - r.GET(PushConf.API.StatAppURI, appStatusHandler) - r.GET(PushConf.API.ConfigURI, configHandler) - r.GET(PushConf.API.SysStatURI, sysStatsHandler) - r.POST(PushConf.API.PushURI, pushHandler) - r.GET(PushConf.API.MetricURI, metricsHandler) - r.GET(PushConf.API.HealthURI, heartbeatHandler) - r.HEAD(PushConf.API.HealthURI, heartbeatHandler) - r.GET("/version", versionHandler) - r.GET("/", rootHandler) - - return r -} diff --git a/gorush/server_lambda.go b/gorush/server_lambda.go deleted file mode 100644 index b4bdbc212..000000000 --- a/gorush/server_lambda.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build lambda - -package gorush - -import ( - "context" - - "github.com/apex/gateway" -) - -// RunHTTPServer provide run http or https protocol. -func RunHTTPServer(ctx context.Context) error { - if !PushConf.Core.Enabled { - LogAccess.Debug("httpd server is disabled.") - return nil - } - - LogAccess.Info("HTTPD server is running on " + PushConf.Core.Port + " port.") - - return gateway.ListenAndServe(PushConf.Core.Address+":"+PushConf.Core.Port, routerEngine()) -} diff --git a/gorush/server_normal.go b/gorush/server_normal.go deleted file mode 100644 index d596e5fd4..000000000 --- a/gorush/server_normal.go +++ /dev/null @@ -1,118 +0,0 @@ -// +build !lambda - -package gorush - -import ( - "context" - "crypto/tls" - "encoding/base64" - "errors" - "net/http" - "time" - - "golang.org/x/sync/errgroup" -) - -// RunHTTPServer provide run http or https protocol. -func RunHTTPServer(ctx context.Context) (err error) { - if !PushConf.Core.Enabled { - LogAccess.Info("httpd server is disabled.") - return nil - } - - server := &http.Server{ - Addr: PushConf.Core.Address + ":" + PushConf.Core.Port, - Handler: routerEngine(), - } - - LogAccess.Info("HTTPD server is running on " + PushConf.Core.Port + " port.") - if PushConf.Core.AutoTLS.Enabled { - return startServer(ctx, autoTLSServer()) - } else if PushConf.Core.SSL { - config := &tls.Config{ - MinVersion: tls.VersionTLS10, - } - - if config.NextProtos == nil { - config.NextProtos = []string{"http/1.1"} - } - - config.Certificates = make([]tls.Certificate, 1) - if PushConf.Core.CertPath != "" && PushConf.Core.KeyPath != "" { - config.Certificates[0], err = tls.LoadX509KeyPair(PushConf.Core.CertPath, PushConf.Core.KeyPath) - if err != nil { - LogError.Error("Failed to load https cert file: ", err) - return err - } - } else if PushConf.Core.CertBase64 != "" && PushConf.Core.KeyBase64 != "" { - cert, err := base64.StdEncoding.DecodeString(PushConf.Core.CertBase64) - if err != nil { - LogError.Error("base64 decode error:", err.Error()) - return err - } - key, err := base64.StdEncoding.DecodeString(PushConf.Core.KeyBase64) - if err != nil { - LogError.Error("base64 decode error:", err.Error()) - return err - } - if config.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil { - LogError.Error("tls key pair error:", err.Error()) - return err - } - } else { - return errors.New("missing https cert config") - } - - server.TLSConfig = config - } - - return startServer(ctx, server) -} - -func listenAndServe(ctx context.Context, s *http.Server) error { - var g errgroup.Group - g.Go(func() error { - select { - case <-ctx.Done(): - timeout := time.Duration(PushConf.Core.ShutdownTimeout) * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return s.Shutdown(ctx) - } - }) - g.Go(func() error { - if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { - return err - } - return nil - }) - return g.Wait() -} - -func listenAndServeTLS(ctx context.Context, s *http.Server) error { - var g errgroup.Group - g.Go(func() error { - select { - case <-ctx.Done(): - timeout := time.Duration(PushConf.Core.ShutdownTimeout) * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return s.Shutdown(ctx) - } - }) - g.Go(func() error { - if err := s.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { - return err - } - return nil - }) - return g.Wait() -} - -func startServer(ctx context.Context, s *http.Server) error { - if s.TLSConfig == nil { - return listenAndServe(ctx, s) - } - - return listenAndServeTLS(ctx, s) -} diff --git a/gorush/server_test.go b/gorush/server_test.go deleted file mode 100644 index b2769b9a0..000000000 --- a/gorush/server_test.go +++ /dev/null @@ -1,398 +0,0 @@ -package gorush - -import ( - "context" - "crypto/tls" - "io/ioutil" - "log" - "net/http" - "os" - "runtime" - "testing" - "time" - - "github.com/appleboy/gorush/config" - - "github.com/appleboy/gofight/v2" - "github.com/buger/jsonparser" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -var goVersion = runtime.Version() - -func initTest() { - PushConf, _ = config.LoadConf("") - PushConf.Core.Mode = "test" -} - -// testRequest is testing url string if server is running -func testRequest(t *testing.T, url string) { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - client := &http.Client{ - Timeout: time.Second * 10, - Transport: tr, - } - - resp, err := client.Get(url) - defer func() { - if err := resp.Body.Close(); err != nil { - log.Println("close body err:", err) - } - }() - - assert.NoError(t, err) - - _, ioerr := ioutil.ReadAll(resp.Body) - assert.NoError(t, ioerr) - assert.Equal(t, "200 OK", resp.Status, "should get a 200") -} - -func TestPrintGoRushVersion(t *testing.T) { - SetVersion("3.0.0") - ver := GetVersion() - PrintGoRushVersion() - - assert.Equal(t, "3.0.0", ver) -} - -func TestRunNormalServer(t *testing.T) { - initTest() - - gin.SetMode(gin.TestMode) - - go func() { - assert.NoError(t, RunHTTPServer(context.Background())) - }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) - - testRequest(t, "http://localhost:8088/api/stat/go") -} - -func TestRunTLSServer(t *testing.T) { - initTest() - - PushConf.Core.SSL = true - PushConf.Core.Port = "8087" - PushConf.Core.CertPath = "../certificate/localhost.cert" - PushConf.Core.KeyPath = "../certificate/localhost.key" - - go func() { - assert.NoError(t, RunHTTPServer(context.Background())) - }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) - - testRequest(t, "https://localhost:8087/api/stat/go") -} - -func TestRunTLSBase64Server(t *testing.T) { - cert := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrekNDQWVPZ0F3SUJBZ0lKQUxiWkVEdlVRckZLTUEwR0NTcUdTSWIzRFFFQkJRVUFNQlF4RWpBUUJnTlYKQkFNTUNXeHZZMkZzYUc5emREQWVGdzB4TmpBek1qZ3dNek13TkRGYUZ3MHlOakF6TWpZd016TXdOREZhTUJReApFakFRQmdOVkJBTU1DV3h2WTJGc2FHOXpkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DCmdnRUJBTWoxK3hnNGpWTHpWbkI1ajduMXVsMzBXRUU0QkN6Y05GeGc1QU9CNUg1cSt3amUwWVlpVkZnNlBReXYKR0NpcHFJUlhWUmRWUTFoSFNldW5ZR0tlOGxxM1NiMVg4UFVKMTJ2OXVSYnBTOURLMU93cWs4cnNQRHU2c1ZUTApxS0tnSDFaOHlhenphUzBBYlh1QTVlOWdPL1J6aWpibnBFUCtxdU00ZHVlaU1QVkVKeUxxK0VvSVFZK01NOE1QCjhkWnpMNFhabDd3TDRVc0NON3JQY082VzN0bG5UMGlPM2g5Yy9ZbTJoRmh6K0tOSjlLUlJDdnRQR1pFU2lndEsKYkhzWEgwOTlXRG84di9XcDUvZXZCdy8rSkQwb3B4bUNmSElCQUxIdDl2NTNSdnZzRFoxdDMzUnB1NUM4em5FWQpZMkF5N05neGhxanFvV0pxQTQ4bEplQTBjbHNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkMwYlRVMVhvZmVoCk5LSWVsYXNoSXNxS2lkRFlNQjhHQTFVZEl3UVlNQmFBRkMwYlRVMVhvZmVoTktJZWxhc2hJc3FLaWREWU1Bd0cKQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFBaUpMOElNVHdOWDlYcVFXWURGZ2tHNApBbnJWd1FocmVBcUM5clN4RENqcXFuTUhQSEd6Y0NlRE1MQU1vaDBrT3kyMG5vd1VHTnRDWjB1QnZuWDJxMWJOCmcxanQrR0JjTEpEUjNMTDRDcE5PbG0zWWhPeWN1TmZXTXhUQTdCWGttblNyWkQvN0toQXJzQkVZOGF1bHh3S0oKSFJnTmxJd2Uxb0ZEMVlkWDFCUzVwcDR0MjVCNlZxNEEzRk1NVWtWb1dFNjg4bkUxNjhodlFnd2pySGtnSGh3ZQplTjhsR0UyRGhGcmFYbldtRE1kd2FIRDNIUkZHaHlwcElGTitmN0JxYldYOWdNK1QyWVJUZk9iSVhMV2JxSkxECjNNay9Oa3hxVmNnNGVZNTR3SjF1ZkNVR0FZQUlhWTZmUXFpTlV6OG5od0szdDQ1TkJWVDl5L3VKWHFuVEx5WT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=` - key := `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeVBYN0dEaU5Vdk5XY0htUHVmVzZYZlJZUVRnRUxOdzBYR0RrQTRIa2ZtcjdDTjdSCmhpSlVXRG85REs4WUtLbW9oRmRWRjFWRFdFZEo2NmRnWXA3eVdyZEp2VmZ3OVFuWGEvMjVGdWxMME1yVTdDcVQKeXV3OE83cXhWTXVvb3FBZlZuekpyUE5wTFFCdGU0RGw3MkE3OUhPS051ZWtRLzZxNHpoMjU2SXc5VVFuSXVyNApTZ2hCajR3end3L3gxbk12aGRtWHZBdmhTd0kzdXM5dzdwYmUyV2RQU0k3ZUgxejlpYmFFV0hQNG8wbjBwRkVLCiswOFprUktLQzBwc2V4Y2ZUMzFZT2p5Lzlhbm45NjhIRC80a1BTaW5HWUo4Y2dFQXNlMzIvbmRHKyt3Tm5XM2YKZEdtN2tMek9jUmhqWURMczJER0dxT3FoWW1vRGp5VWw0RFJ5V3dJREFRQUJBb0lCQUdUS3FzTjlLYlNmQTQycQpDcUkwVXVMb3VKTU5hMXFzbno1dUFpNllLV2dXZEE0QTQ0bXBFakNtRlJTVmhVSnZ4V3VLK2N5WUlRelh4SVdECkQxNm5aZHFGNzJBZUNXWjlKeVNzdnZaMDBHZktNM3kzNWlSeTA4c0pXZ096bWNMbkdKQ2lTZXlLc1FlM0hUSkMKZGhEWGJYcXZzSFRWUFpnMDFMVGVEeFVpVGZmVThOTUtxUjJBZWNRMnNURHdYRWhBblR5QXRuemwvWGFCZ0Z6dQpVNkc3RnpHTTV5OWJ4a2ZRVmt2eStERUprSEdOT2p6d2NWZkJ5eVZsNjEwaXhtRzF2bXhWajlQYldtSVBzVVY4CnlTbWpodkRRYk9mb3hXMGg5dlRsVHFHdFFjQnc5NjJvc25ERE1XRkNkTTdsek8wVDdSUm5QVkdJUnBDSk9LaHEKa2VxSEt3RUNnWUVBOHd3SS9pWnVnaG9UWFRORzlMblFRL1dBdHNxTzgwRWpNVFVoZW81STFrT3ptVXowOXB5aAppQXNVRG9OMC8yNnRaNVdOamxueVp1N2R2VGMveDNkVFpwbU5ub284Z2NWYlFORUNEUnpxZnVROVBQWG0xU041CjZwZUJxQXZCdjc4aGpWMDVhWHpQRy9WQmJlaWc3bDI5OUVhckVBK2Evb0gzS3JnRG9xVnFFMEVDZ1lFQTA2dkEKWUptZ2c0ZlpSdWNBWW9hWXNMejlaOXJDRmpUZTFQQlRtVUprYk9SOHZGSUhIVFRFV2kvU3V4WEwwd0RTZW9FMgo3QlFtODZnQ0M3L0tnUmRyem9CcVo1cVM5TXYyZHNMZ1k2MzVWU2dqamZaa1ZMaUgxVlJScFNRT2JZbmZveXNnCmdhdGNIU0tNRXhkNFNMUUJ5QXVJbVhQK0w1YXlEQmNFSmZicVNwc0NnWUI3OElzMWIwdXpOTERqT2g3WTlWaHIKRDJxUHpFT1JjSW9Oc2RaY3RPb1h1WGFBbW1uZ3lJYm01UjlaTjFnV1djNDdvRndMVjNyeFdxWGdzNmZtZzhjWAo3djMwOXZGY0M5UTQvVnhhYTRCNUxOSzluM2dUQUlCUFRPdGxVbmwrMm15MXRmQnRCcVJtMFc2SUtiVEhXUzVnCnZ4akVtL0NpRUl5R1VFZ3FUTWdIQVFLQmdCS3VYZFFvdXRuZzYzUXVmd0l6RHRiS1Z6TUxRNFhpTktobWJYcGgKT2F2Q25wK2dQYkIrTDdZbDhsdEFtVFNPSmdWWjBoY1QwRHhBMzYxWngrMk11NThHQmw0T2JsbmNobXdFMXZqMQpLY1F5UHJFUXhkb1VUeWlzd0dmcXZyczhKOWltdmIrejkvVTZUMUtBQjhXaTNXVmlYelByNE1zaWFhUlhnNjQyCkZJZHhBb0dBWjcvNzM1ZGtoSmN5T2ZzK0xLc0xyNjhKU3N0b29yWE9ZdmRNdTErSkdhOWlMdWhuSEVjTVZXQzgKSXVpaHpQZmxvWnRNYkdZa1pKbjhsM0JlR2Q4aG1mRnRnVGdaR1BvVlJldGZ0MkxERkxuUHhwMnNFSDVPRkxzUQpSK0sva0FPdWw4ZVN0V3VNWE9GQTlwTXpHa0dFZ0lGSk1KT3lhSk9OM2tlZFFJOGRlQ009Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==` - initTest() - - PushConf.Core.SSL = true - PushConf.Core.Port = "8089" - PushConf.Core.CertPath = "" - PushConf.Core.KeyPath = "" - PushConf.Core.CertBase64 = cert - PushConf.Core.KeyBase64 = key - - go func() { - assert.NoError(t, RunHTTPServer(context.Background())) - }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) - - testRequest(t, "https://localhost:8089/api/stat/go") -} - -func TestRunAutoTLSServer(t *testing.T) { - initTest() - PushConf.Core.AutoTLS.Enabled = true - go func() { - assert.NoError(t, RunHTTPServer(context.Background())) - }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) -} - -func TestLoadTLSCertError(t *testing.T) { - initTest() - - PushConf.Core.SSL = true - PushConf.Core.Port = "8087" - PushConf.Core.CertPath = "../config/config.yml" - PushConf.Core.KeyPath = "../config/config.yml" - - assert.Error(t, RunHTTPServer(context.Background())) -} - -func TestMissingTLSCertConfg(t *testing.T) { - initTest() - - PushConf.Core.SSL = true - PushConf.Core.Port = "8087" - PushConf.Core.CertPath = "" - PushConf.Core.KeyPath = "" - PushConf.Core.CertBase64 = "" - PushConf.Core.KeyBase64 = "" - - err := RunHTTPServer(context.Background()) - assert.Error(t, RunHTTPServer(context.Background())) - assert.Equal(t, "missing https cert config", err.Error()) -} - -func TestRootHandler(t *testing.T) { - initTest() - - r := gofight.New() - - // log for json - PushConf.Log.Format = "json" - - r.GET("/"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - data := r.Body.Bytes() - - value, _ := jsonparser.GetString(data, "text") - - assert.Equal(t, "Welcome to notification server.", value) - assert.Equal(t, http.StatusOK, r.Code) - assert.Equal(t, "application/json; charset=utf-8", r.HeaderMap.Get("Content-Type")) - }) -} - -func TestAPIStatusGoHandler(t *testing.T) { - initTest() - - r := gofight.New() - - r.GET("/api/stat/go"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - data := r.Body.Bytes() - - value, _ := jsonparser.GetString(data, "go_version") - - assert.Equal(t, goVersion, value) - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestAPIStatusAppHandler(t *testing.T) { - initTest() - - r := gofight.New() - - appVersion := "v1.0.0" - SetVersion(appVersion) - - r.GET("/api/stat/app"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - data := r.Body.Bytes() - - value, _ := jsonparser.GetString(data, "version") - - assert.Equal(t, appVersion, value) - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestAPIConfigHandler(t *testing.T) { - initTest() - - r := gofight.New() - - r.GET("/api/config"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusCreated, r.Code) - }) -} - -func TestMissingNotificationsParameter(t *testing.T) { - initTest() - - r := gofight.New() - - // missing notifications parameter. - r.POST("/api/push"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusBadRequest, r.Code) - assert.Equal(t, "application/json; charset=utf-8", r.HeaderMap.Get("Content-Type")) - }) -} - -func TestEmptyNotifications(t *testing.T) { - initTest() - - r := gofight.New() - - // notifications is empty. - r.POST("/api/push"). - SetJSON(gofight.D{ - "notifications": []PushNotification{}, - }). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusBadRequest, r.Code) - }) -} - -func TestMutableContent(t *testing.T) { - initTest() - - r := gofight.New() - - // notifications is empty. - r.POST("/api/push"). - SetJSON(gofight.D{ - "notifications": []gofight.D{ - { - "tokens": []string{"aaaaa", "bbbbb"}, - "platform": PlatFormAndroid, - "message": "Welcome", - "mutable_content": 1, - "topic": "test", - "badge": 1, - "alert": gofight.D{ - "title": "title", - "body": "body", - }, - }, - }, - }). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - // json: cannot unmarshal number into Go struct field PushNotification.mutable_content of type bool - assert.Equal(t, http.StatusBadRequest, r.Code) - }) -} - -func TestOutOfRangeMaxNotifications(t *testing.T) { - initTest() - - PushConf.Core.MaxNotification = int64(1) - - r := gofight.New() - - // notifications is empty. - r.POST("/api/push"). - SetJSON(gofight.D{ - "notifications": []gofight.D{ - { - "tokens": []string{"aaaaa", "bbbbb"}, - "platform": PlatFormAndroid, - "message": "Welcome", - }, - { - "tokens": []string{"aaaaa", "bbbbb"}, - "platform": PlatFormAndroid, - "message": "Welcome", - }, - }, - }). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusBadRequest, r.Code) - }) -} - -func TestSuccessPushHandler(t *testing.T) { - t.Skip() - initTest() - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - - androidToken := os.Getenv("ANDROID_TEST_TOKEN") - - r := gofight.New() - - r.POST("/api/push"). - SetJSON(gofight.D{ - "notifications": []gofight.D{ - { - "tokens": []string{androidToken, "bbbbb"}, - "platform": PlatFormAndroid, - "message": "Welcome", - }, - }, - }). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestSysStatsHandler(t *testing.T) { - initTest() - - r := gofight.New() - - r.GET("/sys/stats"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestMetricsHandler(t *testing.T) { - initTest() - - r := gofight.New() - - r.GET("/metrics"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestGETHeartbeatHandler(t *testing.T) { - initTest() - - r := gofight.New() - - r.GET("/healthz"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestHEADHeartbeatHandler(t *testing.T) { - initTest() - - r := gofight.New() - - r.HEAD("/healthz"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusOK, r.Code) - }) -} - -func TestVersionHandler(t *testing.T) { - SetVersion("3.0.0") - initTest() - - r := gofight.New() - - r.GET("/version"). - Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - assert.Equal(t, http.StatusOK, r.Code) - data := r.Body.Bytes() - - value, _ := jsonparser.GetString(data, "version") - - assert.Equal(t, "3.0.0", value) - }) -} - -func TestDisabledHTTPServer(t *testing.T) { - initTest() - PushConf.Core.Enabled = false - err := RunHTTPServer(context.Background()) - PushConf.Core.Enabled = true - - assert.Nil(t, err) -} diff --git a/gorush/status.go b/gorush/status.go deleted file mode 100644 index 7495411d9..000000000 --- a/gorush/status.go +++ /dev/null @@ -1,108 +0,0 @@ -package gorush - -import ( - "errors" - "net/http" - - "github.com/appleboy/gorush/storage/badger" - "github.com/appleboy/gorush/storage/boltdb" - "github.com/appleboy/gorush/storage/buntdb" - "github.com/appleboy/gorush/storage/leveldb" - "github.com/appleboy/gorush/storage/memory" - "github.com/appleboy/gorush/storage/redis" - - "github.com/gin-gonic/gin" - "github.com/thoas/stats" -) - -// Stats provide response time, status code count, etc. -var Stats = stats.New() - -// StatusApp is app status structure -type StatusApp struct { - Version string `json:"version"` - QueueMax int `json:"queue_max"` - QueueUsage int `json:"queue_usage"` - TotalCount int64 `json:"total_count"` - Ios IosStatus `json:"ios"` - Android AndroidStatus `json:"android"` - Huawei HuaweiStatus `json:"huawei"` -} - -// AndroidStatus is android structure -type AndroidStatus struct { - PushSuccess int64 `json:"push_success"` - PushError int64 `json:"push_error"` -} - -// IosStatus is iOS structure -type IosStatus struct { - PushSuccess int64 `json:"push_success"` - PushError int64 `json:"push_error"` -} - -// HuaweiStatus is huawei structure -type HuaweiStatus struct { - PushSuccess int64 `json:"push_success"` - PushError int64 `json:"push_error"` -} - -// InitAppStatus for initialize app status -func InitAppStatus() error { - LogAccess.Info("Init App Status Engine as ", PushConf.Stat.Engine) - switch PushConf.Stat.Engine { - case "memory": - StatStorage = memory.New() - case "redis": - StatStorage = redis.New(PushConf) - case "boltdb": - StatStorage = boltdb.New(PushConf) - case "buntdb": - StatStorage = buntdb.New(PushConf) - case "leveldb": - StatStorage = leveldb.New(PushConf) - case "badger": - StatStorage = badger.New(PushConf) - default: - LogError.Error("storage error: can't find storage driver") - return errors.New("can't find storage driver") - } - - if err := StatStorage.Init(); err != nil { - LogError.Error("storage error: " + err.Error()) - - return err - } - - return nil -} - -func appStatusHandler(c *gin.Context) { - result := StatusApp{} - - result.Version = GetVersion() - result.QueueMax = cap(QueueNotification) - result.QueueUsage = len(QueueNotification) - result.TotalCount = StatStorage.GetTotalCount() - result.Ios.PushSuccess = StatStorage.GetIosSuccess() - result.Ios.PushError = StatStorage.GetIosError() - result.Android.PushSuccess = StatStorage.GetAndroidSuccess() - result.Android.PushError = StatStorage.GetAndroidError() - result.Huawei.PushSuccess = StatStorage.GetHuaweiSuccess() - result.Huawei.PushError = StatStorage.GetHuaweiError() - - c.JSON(http.StatusOK, result) -} - -func sysStatsHandler(c *gin.Context) { - c.JSON(http.StatusOK, Stats.Data()) -} - -// StatMiddleware response time, status code count, etc. -func StatMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - beginning, recorder := Stats.Begin(c.Writer) - c.Next() - Stats.End(beginning, stats.WithRecorder(recorder)) - } -} diff --git a/gorush/worker.go b/gorush/worker.go deleted file mode 100644 index 362ae4b82..000000000 --- a/gorush/worker.go +++ /dev/null @@ -1,111 +0,0 @@ -package gorush - -import ( - "context" - "errors" - "sync" -) - -// InitWorkers for initialize all workers. -func InitWorkers(ctx context.Context, wg *sync.WaitGroup, workerNum, queueNum int64) { - LogAccess.Info("worker number is ", workerNum, ", queue number is ", queueNum) - QueueNotification = make(chan PushNotification, queueNum) - for i := int64(0); i < workerNum; i++ { - go startWorker(ctx, wg, i) - } -} - -// SendNotification is send message to iOS, Android or Huawei -func SendNotification(ctx context.Context, req PushNotification) { - if PushConf.Core.Sync { - defer req.WaitDone() - } - - switch req.Platform { - case PlatFormIos: - PushToIOS(req) - case PlatFormAndroid: - PushToAndroid(req) - case PlatFormHuawei: - PushToHuawei(req) - } -} - -func startWorker(ctx context.Context, wg *sync.WaitGroup, num int64) { - defer wg.Done() - for notification := range QueueNotification { - SendNotification(ctx, notification) - } - LogAccess.Info("closed the worker num ", num) -} - -// markFailedNotification adds failure logs for all tokens in push notification -func markFailedNotification(notification *PushNotification, reason string) { - LogError.Error(reason) - for _, token := range notification.Tokens { - notification.AddLog(getLogPushEntry(FailedPush, token, *notification, errors.New(reason))) - } - notification.WaitDone() -} - -// queueNotification add notification to queue list. -func queueNotification(ctx context.Context, req RequestPush) (int, []LogPushEntry) { - var count int - wg := sync.WaitGroup{} - newNotification := []*PushNotification{} - for i := range req.Notifications { - notification := &req.Notifications[i] - switch notification.Platform { - case PlatFormIos: - if !PushConf.Ios.Enabled { - continue - } - case PlatFormAndroid: - if !PushConf.Android.Enabled { - continue - } - case PlatFormHuawei: - if !PushConf.Huawei.Enabled { - continue - } - } - newNotification = append(newNotification, notification) - } - - log := make([]LogPushEntry, 0, count) - for _, notification := range newNotification { - if PushConf.Core.Sync { - notification.wg = &wg - notification.log = &log - notification.AddWaitCount() - } - if !tryEnqueue(*notification, QueueNotification) { - markFailedNotification(notification, "max capacity reached") - } - count += len(notification.Tokens) - // Count topic message - if notification.To != "" { - count++ - } - } - - if PushConf.Core.Sync { - wg.Wait() - } - - StatStorage.AddTotalCount(int64(count)) - - return count, log -} - -// tryEnqueue tries to enqueue a job to the given job channel. Returns true if -// the operation was successful, and false if enqueuing would not have been -// possible without blocking. Job is not enqueued in the latter case. -func tryEnqueue(job PushNotification, jobChan chan<- PushNotification) bool { - select { - case jobChan <- job: - return true - default: - return false - } -} diff --git a/gorush/worker_test.go b/gorush/worker_test.go deleted file mode 100644 index 1f223194b..000000000 --- a/gorush/worker_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package gorush - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTryEnqueue(t *testing.T) { - chn := make(chan PushNotification, 2) - assert.True(t, tryEnqueue(PushNotification{}, chn)) - assert.Equal(t, 1, len(chn)) - assert.True(t, tryEnqueue(PushNotification{}, chn)) - assert.Equal(t, 2, len(chn)) - assert.False(t, tryEnqueue(PushNotification{}, chn)) - assert.Equal(t, 2, len(chn)) -} diff --git a/helm/gorush/.helmignore b/helm/gorush/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/gorush/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/gorush/Chart.yaml b/helm/gorush/Chart.yaml new file mode 100644 index 000000000..ea3df48bd --- /dev/null +++ b/helm/gorush/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: gorush +description: A push notification micro server using Gin framework written in Go (Golang) +type: application +version: 0.1.0 +appVersion: "1.14.0" +dependencies: + - name: redis + version: ~14.1 + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled diff --git a/helm/gorush/templates/NOTES.txt b/helm/gorush/templates/NOTES.txt new file mode 100644 index 000000000..2d942df59 --- /dev/null +++ b/helm/gorush/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "gorush.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "gorush.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "gorush.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "gorush.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/gorush/templates/_helpers.tpl b/helm/gorush/templates/_helpers.tpl new file mode 100644 index 000000000..f46bea8a4 --- /dev/null +++ b/helm/gorush/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "gorush.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "gorush.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "gorush.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "gorush.labels" -}} +helm.sh/chart: {{ include "gorush.chart" . }} +{{ include "gorush.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "gorush.selectorLabels" -}} +app.kubernetes.io/name: {{ include "gorush.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "gorush.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "gorush.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/gorush/templates/configmap.yml b/helm/gorush/templates/configmap.yml new file mode 100644 index 000000000..ba9617661 --- /dev/null +++ b/helm/gorush/templates/configmap.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} +data: + # stat + stats: + engine: {{ .Values.stat.engine }} + {{- if .Values.redis.enabled }} + redis: + host: {{ .Values.redis.host }}:{{ .Values.redis.port }} + {{- end }} diff --git a/helm/gorush/templates/deployment.yaml b/helm/gorush/templates/deployment.yaml new file mode 100644 index 000000000..3d9849071 --- /dev/null +++ b/helm/gorush/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "gorush.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "gorush.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "gorush.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "gorush.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 15 + periodSeconds: 15 + env: + - name: GORUSH_STAT_ENGINE + valueFrom: + configMapKeyRef: + name: {{ .Chart.Name }}-config + key: stat.engine + {{- if .Values.redis.enabled }} + - name: GORUSH_STAT_REDIS_ADDR + valueFrom: + configMapKeyRef: + name: {{ .Chart.Name }}-config + key: stat.redis.host + {{- end }} + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/gorush/templates/hpa.yaml b/helm/gorush/templates/hpa.yaml new file mode 100644 index 000000000..b8d1df76b --- /dev/null +++ b/helm/gorush/templates/hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Chart.Name }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/gorush/templates/ingress.yaml b/helm/gorush/templates/ingress.yaml new file mode 100644 index 000000000..623586316 --- /dev/null +++ b/helm/gorush/templates/ingress.yaml @@ -0,0 +1,44 @@ +{{- if .Values.ingress.enabled -}} +{{- $svcPort := .Values.service.port -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: Prefix + backend: + service: + name: gorush + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} diff --git a/helm/gorush/templates/service.yaml b/helm/gorush/templates/service.yaml new file mode 100644 index 000000000..d3846cebd --- /dev/null +++ b/helm/gorush/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "gorush.selectorLabels" . | nindent 4 }} diff --git a/helm/gorush/templates/serviceaccount.yaml b/helm/gorush/templates/serviceaccount.yaml new file mode 100644 index 000000000..67a5df568 --- /dev/null +++ b/helm/gorush/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gorush.serviceAccountName" . }} + namespace: {{ .Chart.Name }} + labels: + {{- include "gorush.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/gorush/values.yaml b/helm/gorush/values.yaml new file mode 100644 index 000000000..6fc42db1f --- /dev/null +++ b/helm/gorush/values.yaml @@ -0,0 +1,73 @@ +# Default values for gorush. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: appleboy/gorush + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: false + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8088 + +stats: + engine: memory + +redis: + enabled: false + host: redis + port: 6379 + +ingress: + enabled: false + annotations: {} + hosts: + - host: gorush.example.com + paths: + - path: / + tls: [] + +resources: {} + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/gorush/log.go b/logx/log.go similarity index 66% rename from gorush/log.go rename to logx/log.go index 9364e0b23..056ac5b67 100644 --- a/gorush/log.go +++ b/logx/log.go @@ -1,4 +1,4 @@ -package gorush +package logx import ( "encoding/json" @@ -7,19 +7,18 @@ import ( "os" "strings" + "github.com/appleboy/gorush/core" + "github.com/mattn/go-isatty" "github.com/sirupsen/logrus" ) var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + reset = string([]byte{27, 91, 48, 109}) ) // LogPushEntry is push response log @@ -38,13 +37,16 @@ func init() { isTerm = isatty.IsTerminal(os.Stdout.Fd()) } -// InitLog use for initial log module -func InitLog() error { - var err error - - // init logger +var ( + // LogAccess is log server request log LogAccess = logrus.New() + // LogError is log server error log LogError = logrus.New() +) + +// InitLog use for initial log module +func InitLog(accessLevel, accessLog, errorLevel, errorLog string) error { + var err error if !isTerm { LogAccess.SetFormatter(&logrus.JSONFormatter{}) @@ -62,19 +64,19 @@ func InitLog() error { } // set logger - if err = SetLogLevel(LogAccess, PushConf.Log.AccessLevel); err != nil { + if err = SetLogLevel(LogAccess, accessLevel); err != nil { return errors.New("Set access log level error: " + err.Error()) } - if err = SetLogLevel(LogError, PushConf.Log.ErrorLevel); err != nil { + if err = SetLogLevel(LogError, errorLevel); err != nil { return errors.New("Set error log level error: " + err.Error()) } - if err = SetLogOut(LogAccess, PushConf.Log.AccessLog); err != nil { + if err = SetLogOut(LogAccess, accessLog); err != nil { return errors.New("Set access log path error: " + err.Error()) } - if err = SetLogOut(LogError, PushConf.Log.ErrorLog); err != nil { + if err = SetLogOut(LogError, errorLog); err != nil { return errors.New("Set error log path error: " + err.Error()) } @@ -115,11 +117,11 @@ func SetLogLevel(log *logrus.Logger, levelString string) error { func colorForPlatForm(platform int) string { switch platform { - case PlatFormIos: + case core.PlatFormIos: return blue - case PlatFormAndroid: + case core.PlatFormAndroid: return yellow - case PlatFormHuawei: + case core.PlatFormHuawei: return green default: return reset @@ -128,11 +130,11 @@ func colorForPlatForm(platform int) string { func typeForPlatForm(platform int) string { switch platform { - case PlatFormIos: + case core.PlatFormIos: return "ios" - case PlatFormAndroid: + case core.PlatFormAndroid: return "android" - case PlatFormHuawei: + case core.PlatFormHuawei: return "huawei" default: return "" @@ -157,48 +159,62 @@ func hideToken(token string, markLen int) string { return result } -func getLogPushEntry(status, token string, req PushNotification, errPush error) LogPushEntry { +// GetLogPushEntry get push data into log structure +func GetLogPushEntry(input *InputLog) LogPushEntry { var errMsg string + var token string - plat := typeForPlatForm(req.Platform) + plat := typeForPlatForm(input.Platform) - if errPush != nil { - errMsg = errPush.Error() + if input.Error != nil { + errMsg = input.Error.Error() } - if PushConf.Log.HideToken { - token = hideToken(token, 10) + if input.HideToken { + token = hideToken(input.Token, 10) } return LogPushEntry{ - ID: req.ID, - Type: status, + ID: input.ID, + Type: input.Status, Platform: plat, Token: token, - Message: req.Message, + Message: input.Message, Error: errMsg, } } +// InputLog log request +type InputLog struct { + ID string + Status string + Token string + Message string + Platform int + Error error + HideToken bool + Format string +} + // LogPush record user push request and server response. -func LogPush(status, token string, req PushNotification, errPush error) { +func LogPush(input *InputLog) LogPushEntry { var platColor, resetColor, output string if isTerm { - platColor = colorForPlatForm(req.Platform) + platColor = colorForPlatForm(input.Platform) resetColor = reset } - log := getLogPushEntry(status, token, req, errPush) + log := GetLogPushEntry(input) - if PushConf.Log.Format == "json" { + if input.Format == "json" { logJSON, _ := json.Marshal(log) output = string(logJSON) } else { var typeColor string - switch status { - case SucceededPush: + switch input.Status { + case core.SucceededPush: if isTerm { typeColor = green } @@ -209,7 +225,7 @@ func LogPush(status, token string, req PushNotification, errPush error) { log.Token, log.Message, ) - case FailedPush: + case core.FailedPush: if isTerm { typeColor = red } @@ -224,10 +240,12 @@ func LogPush(status, token string, req PushNotification, errPush error) { } } - switch status { - case SucceededPush: + switch input.Status { + case core.SucceededPush: LogAccess.Info(output) - case FailedPush: + case core.FailedPush: LogError.Error(output) } + + return log } diff --git a/gorush/log/.gitkeep b/logx/log/.gitkeep similarity index 100% rename from gorush/log/.gitkeep rename to logx/log/.gitkeep diff --git a/logx/log/access.log b/logx/log/access.log new file mode 100644 index 000000000..e69de29bb diff --git a/logx/log_interface.go b/logx/log_interface.go new file mode 100644 index 000000000..51a564cb3 --- /dev/null +++ b/logx/log_interface.go @@ -0,0 +1,45 @@ +package logx + +import ( + "fmt" + + "github.com/sirupsen/logrus" +) + +// QueueLogger for simple logger. +func QueueLogger() DefaultQueueLogger { + return DefaultQueueLogger{ + accessLogger: LogAccess, + errorLogger: LogError, + } +} + +// DefaultQueueLogger for queue custom logger +type DefaultQueueLogger struct { + accessLogger *logrus.Logger + errorLogger *logrus.Logger +} + +func (l DefaultQueueLogger) Infof(format string, args ...interface{}) { + l.accessLogger.Printf(format, args...) +} + +func (l DefaultQueueLogger) Errorf(format string, args ...interface{}) { + l.errorLogger.Printf(format, args...) +} + +func (l DefaultQueueLogger) Fatalf(format string, args ...interface{}) { + l.errorLogger.Fatalf(format, args...) +} + +func (l DefaultQueueLogger) Info(args ...interface{}) { + l.accessLogger.Println(fmt.Sprint(args...)) +} + +func (l DefaultQueueLogger) Error(args ...interface{}) { + l.errorLogger.Println(fmt.Sprint(args...)) +} + +func (l DefaultQueueLogger) Fatal(args ...interface{}) { + l.errorLogger.Println(fmt.Sprint(args...)) +} diff --git a/logx/log_test.go b/logx/log_test.go new file mode 100644 index 000000000..ee1ed3365 --- /dev/null +++ b/logx/log_test.go @@ -0,0 +1,129 @@ +package logx + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestSetLogLevel(t *testing.T) { + log := logrus.New() + + err := SetLogLevel(log, "debug") + assert.Nil(t, err) + + err = SetLogLevel(log, "invalid") + assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) +} + +func TestSetLogOut(t *testing.T) { + log := logrus.New() + + err := SetLogOut(log, "stdout") + assert.Nil(t, err) + + err = SetLogOut(log, "stderr") + assert.Nil(t, err) + + err = SetLogOut(log, "log/access.log") + assert.Nil(t, err) + + // missing create logs folder. + err = SetLogOut(log, "logs/access.log") + assert.NotNil(t, err) +} + +func TestInitDefaultLog(t *testing.T) { + cfg, _ := config.LoadConf() + + // no errors on default config + assert.Nil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) + + cfg.Log.AccessLevel = "invalid" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestAccessLevel(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.AccessLevel = "invalid" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestErrorLevel(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.ErrorLevel = "invalid" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestAccessLogPath(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.AccessLog = "logs/access.log" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestErrorLogPath(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Log.ErrorLog = "logs/error.log" + + assert.NotNil(t, InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + )) +} + +func TestPlatFormType(t *testing.T) { + assert.Equal(t, "ios", typeForPlatForm(core.PlatFormIos)) + assert.Equal(t, "android", typeForPlatForm(core.PlatFormAndroid)) + assert.Equal(t, "", typeForPlatForm(10000)) +} + +func TestPlatFormColor(t *testing.T) { + assert.Equal(t, blue, colorForPlatForm(core.PlatFormIos)) + assert.Equal(t, yellow, colorForPlatForm(core.PlatFormAndroid)) + assert.Equal(t, reset, colorForPlatForm(1000000)) +} + +func TestHideToken(t *testing.T) { + assert.Equal(t, "", hideToken("", 2)) + assert.Equal(t, "**345678**", hideToken("1234567890", 2)) + assert.Equal(t, "*****", hideToken("12345", 10)) +} diff --git a/main.go b/main.go index c518e785d..b4dab1cd7 100644 --- a/main.go +++ b/main.go @@ -11,14 +11,20 @@ import ( "os/signal" "path/filepath" "strconv" - "sync" "syscall" "time" "github.com/appleboy/gorush/config" - "github.com/appleboy/gorush/gorush" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/router" "github.com/appleboy/gorush/rpc" + "github.com/appleboy/gorush/status" + "github.com/golang-queue/nats" + "github.com/golang-queue/nsq" + "github.com/golang-queue/queue" "golang.org/x/sync/errgroup" ) @@ -54,7 +60,7 @@ func main() { ) flag.BoolVar(&showVersion, "version", false, "Print version information.") - flag.BoolVar(&showVersion, "v", false, "Print version information.") + flag.BoolVar(&showVersion, "V", false, "Print version information.") flag.StringVar(&configFile, "c", "", "Configuration file path.") flag.StringVar(&configFile, "config", "", "Configuration file path.") flag.StringVar(&opts.Core.PID.Path, "pid", "", "PID file path.") @@ -93,18 +99,16 @@ func main() { flag.Usage = usage flag.Parse() - gorush.SetVersion(Version) + router.SetVersion(Version) // Show version and exit if showVersion { - gorush.PrintGoRushVersion() + router.PrintGoRushVersion() os.Exit(0) } - var err error - // set default parameters. - gorush.PushConf, err = config.LoadConf(configFile) + cfg, err := config.LoadConf(configFile) if err != nil { log.Printf("Load yaml config file error: '%v'", err) @@ -112,80 +116,85 @@ func main() { } // Initialize push slots for concurrent iOS pushes - gorush.MaxConcurrentIOSPushes = make(chan struct{}, gorush.PushConf.Ios.MaxConcurrentPushes) + notify.MaxConcurrentIOSPushes = make(chan struct{}, cfg.Ios.MaxConcurrentPushes) if opts.Ios.KeyPath != "" { - gorush.PushConf.Ios.KeyPath = opts.Ios.KeyPath + cfg.Ios.KeyPath = opts.Ios.KeyPath } if opts.Ios.KeyID != "" { - gorush.PushConf.Ios.KeyID = opts.Ios.KeyID + cfg.Ios.KeyID = opts.Ios.KeyID } if opts.Ios.TeamID != "" { - gorush.PushConf.Ios.TeamID = opts.Ios.TeamID + cfg.Ios.TeamID = opts.Ios.TeamID } if opts.Ios.Password != "" { - gorush.PushConf.Ios.Password = opts.Ios.Password + cfg.Ios.Password = opts.Ios.Password } if opts.Android.APIKey != "" { - gorush.PushConf.Android.APIKey = opts.Android.APIKey + cfg.Android.APIKey = opts.Android.APIKey } if opts.Huawei.AppSecret != "" { - gorush.PushConf.Huawei.AppSecret = opts.Huawei.AppSecret + cfg.Huawei.AppSecret = opts.Huawei.AppSecret } if opts.Huawei.AppID != "" { - gorush.PushConf.Huawei.AppID = opts.Huawei.AppID + cfg.Huawei.AppID = opts.Huawei.AppID } if opts.Stat.Engine != "" { - gorush.PushConf.Stat.Engine = opts.Stat.Engine + cfg.Stat.Engine = opts.Stat.Engine } if opts.Stat.Redis.Addr != "" { - gorush.PushConf.Stat.Redis.Addr = opts.Stat.Redis.Addr + cfg.Stat.Redis.Addr = opts.Stat.Redis.Addr } // overwrite server port and address if opts.Core.Port != "" { - gorush.PushConf.Core.Port = opts.Core.Port + cfg.Core.Port = opts.Core.Port } if opts.Core.Address != "" { - gorush.PushConf.Core.Address = opts.Core.Address + cfg.Core.Address = opts.Core.Address } - if err = gorush.InitLog(); err != nil { - log.Fatalf("Can't load log module, error: %v", err) + if err = logx.InitLog( + cfg.Log.AccessLevel, + cfg.Log.AccessLog, + cfg.Log.ErrorLevel, + cfg.Log.ErrorLog, + ); err != nil { + log.Fatalf("can't load log module, error: %v", err) } if opts.Core.HTTPProxy != "" { - gorush.PushConf.Core.HTTPProxy = opts.Core.HTTPProxy + cfg.Core.HTTPProxy = opts.Core.HTTPProxy } - if gorush.PushConf.Core.HTTPProxy != "" { - err = gorush.SetProxy(gorush.PushConf.Core.HTTPProxy) + if cfg.Core.HTTPProxy != "" { + err = notify.SetProxy(cfg.Core.HTTPProxy) if err != nil { - gorush.LogError.Fatalf("Set Proxy error: %v", err) + logx.LogError.Fatalf("Set Proxy error: %v", err) } } if ping { - if err := pinger(); err != nil { - gorush.LogError.Warnf("ping server error: %v", err) + if err := pinger(cfg); err != nil { + logx.LogError.Warnf("ping server error: %v", err) } return } // send android notification if opts.Android.Enabled { - gorush.PushConf.Android.Enabled = opts.Android.Enabled - req := gorush.PushNotification{ - Platform: gorush.PlatFormAndroid, + cfg.Android.Enabled = opts.Android.Enabled + req := ¬ify.PushNotification{ + Platform: core.PlatFormAndroid, Message: message, Title: title, } @@ -200,25 +209,27 @@ func main() { req.To = topic } - err := gorush.CheckMessage(req) + err := notify.CheckMessage(req) if err != nil { - gorush.LogError.Fatal(err) + logx.LogError.Fatal(err) } - if err := gorush.InitAppStatus(); err != nil { + if err := status.InitAppStatus(cfg); err != nil { return } - gorush.PushToAndroid(req) + if _, err := notify.PushToAndroid(req, cfg); err != nil { + return + } return } // send huawei notification if opts.Huawei.Enabled { - gorush.PushConf.Huawei.Enabled = opts.Huawei.Enabled - req := gorush.PushNotification{ - Platform: gorush.PlatFormHuawei, + cfg.Huawei.Enabled = opts.Huawei.Enabled + req := ¬ify.PushNotification{ + Platform: core.PlatFormHuawei, Message: message, Title: title, } @@ -233,16 +244,18 @@ func main() { req.To = topic } - err := gorush.CheckMessage(req) + err := notify.CheckMessage(req) if err != nil { - gorush.LogError.Fatal(err) + logx.LogError.Fatal(err) } - if err := gorush.InitAppStatus(); err != nil { + if err := status.InitAppStatus(cfg); err != nil { return } - gorush.PushToHuawei(req) + if _, err := notify.PushToHuawei(req, cfg); err != nil { + return + } return } @@ -250,12 +263,12 @@ func main() { // send ios notification if opts.Ios.Enabled { if opts.Ios.Production { - gorush.PushConf.Ios.Production = opts.Ios.Production + cfg.Ios.Production = opts.Ios.Production } - gorush.PushConf.Ios.Enabled = opts.Ios.Enabled - req := gorush.PushNotification{ - Platform: gorush.PlatFormIos, + cfg.Ios.Enabled = opts.Ios.Enabled + req := ¬ify.PushNotification{ + Platform: core.PlatFormIos, Message: message, Title: title, } @@ -270,74 +283,107 @@ func main() { req.Topic = topic } - err := gorush.CheckMessage(req) + err := notify.CheckMessage(req) if err != nil { - gorush.LogError.Fatal(err) + logx.LogError.Fatal(err) } - if err := gorush.InitAppStatus(); err != nil { + if err := status.InitAppStatus(cfg); err != nil { return } - if err := gorush.InitAPNSClient(); err != nil { + if err := notify.InitAPNSClient(cfg); err != nil { + return + } + + if _, err := notify.PushToIOS(req, cfg); err != nil { return } - gorush.PushToIOS(req) return } - if err = gorush.CheckPushConf(); err != nil { - gorush.LogError.Fatal(err) + if err = notify.CheckPushConf(cfg); err != nil { + logx.LogError.Fatal(err) } if opts.Core.PID.Path != "" { - gorush.PushConf.Core.PID.Path = opts.Core.PID.Path - gorush.PushConf.Core.PID.Enabled = true - gorush.PushConf.Core.PID.Override = true - } - - if err = createPIDFile(); err != nil { - gorush.LogError.Fatal(err) - } - - if err = gorush.InitAppStatus(); err != nil { - gorush.LogError.Fatal(err) - } + cfg.Core.PID.Path = opts.Core.PID.Path + cfg.Core.PID.Enabled = true + cfg.Core.PID.Override = true + } + + if err = createPIDFile(cfg); err != nil { + logx.LogError.Fatal(err) + } + + if err = status.InitAppStatus(cfg); err != nil { + logx.LogError.Fatal(err) + } + + var w queue.Worker + switch core.Queue(cfg.Queue.Engine) { + case core.LocalQueue: + w = queue.NewConsumer( + queue.WithQueueSize(int(cfg.Core.QueueNum)), + queue.WithFn(notify.Run(cfg)), + queue.WithLogger(logx.QueueLogger()), + ) + case core.NSQ: + w = nsq.NewWorker( + nsq.WithAddr(cfg.Queue.NSQ.Addr), + nsq.WithTopic(cfg.Queue.NSQ.Topic), + nsq.WithChannel(cfg.Queue.NSQ.Channel), + nsq.WithMaxInFlight(int(cfg.Core.WorkerNum)), + nsq.WithRunFunc(notify.Run(cfg)), + nsq.WithLogger(logx.QueueLogger()), + ) + case core.NATS: + w = nats.NewWorker( + nats.WithAddr(cfg.Queue.NATS.Addr), + nats.WithSubj(cfg.Queue.NATS.Subj), + nats.WithQueue(cfg.Queue.NATS.Queue), + nats.WithRunFunc(notify.Run(cfg)), + nats.WithLogger(logx.QueueLogger()), + ) + default: + logx.LogError.Fatalf("we don't support queue engine: %s", cfg.Queue.Engine) + } + + q := queue.NewPool( + int(cfg.Core.WorkerNum), + queue.WithWorker(w), + queue.WithLogger(logx.QueueLogger()), + ) finished := make(chan struct{}) - wg := &sync.WaitGroup{} - wg.Add(int(gorush.PushConf.Core.WorkerNum)) ctx := withContextFunc(context.Background(), func() { - gorush.LogAccess.Info("close the notification queue channel, current queue len: ", len(gorush.QueueNotification)) - close(gorush.QueueNotification) - wg.Wait() - gorush.LogAccess.Info("the notification queue has been clear") + logx.LogAccess.Info("close the queue system, current queue usage: ", q.Usage()) + // stop queue system and wait job completed + q.Release() close(finished) // close the connection with storage - gorush.LogAccess.Info("close the storage connection: ", gorush.PushConf.Stat.Engine) - if err := gorush.StatStorage.Close(); err != nil { - gorush.LogError.Fatal("can't close the storage connection: ", err.Error()) + logx.LogAccess.Info("close the storage connection: ", cfg.Stat.Engine) + if err := status.StatStorage.Close(); err != nil { + logx.LogError.Fatal("can't close the storage connection: ", err.Error()) } }) - gorush.InitWorkers(ctx, wg, gorush.PushConf.Core.WorkerNum, gorush.PushConf.Core.QueueNum) - - if gorush.PushConf.Ios.Enabled { - if err = gorush.InitAPNSClient(); err != nil { - gorush.LogError.Fatal(err) + if cfg.Ios.Enabled { + if err = notify.InitAPNSClient(cfg); err != nil { + logx.LogError.Fatal(err) } } - if gorush.PushConf.Android.Enabled { - if _, err = gorush.InitFCMClient(gorush.PushConf.Android.APIKey); err != nil { - gorush.LogError.Fatal(err) + if cfg.Android.Enabled { + if _, err = notify.InitFCMClient(cfg, cfg.Android.APIKey); err != nil { + logx.LogError.Fatal(err) } } - if gorush.PushConf.Huawei.Enabled { - if _, err = gorush.InitHMSClient(gorush.PushConf.Huawei.AppSecret, gorush.PushConf.Huawei.AppID); err != nil { - gorush.LogError.Fatal(err) + if cfg.Huawei.Enabled { + if _, err = notify.InitHMSClient(cfg, cfg.Huawei.AppSecret, cfg.Huawei.AppID); err != nil { + logx.LogError.Fatal(err) } } @@ -345,28 +391,26 @@ func main() { // Run httpd server g.Go(func() error { - return gorush.RunHTTPServer(ctx) + return router.RunHTTPServer(ctx, cfg, q) }) // Run gRPC internal server g.Go(func() error { - return rpc.RunGRPCServer(ctx) + return rpc.RunGRPCServer(ctx, cfg) }) // check job completely g.Go(func() error { - select { - case <-finished: - } + <-finished return nil }) if err = g.Wait(); err != nil { - gorush.LogError.Fatal(err) + logx.LogError.Fatal(err) } } -// Version control for gorush. +// Version control for notify. var Version = "No Version Provided" var usageStr = ` @@ -406,7 +450,7 @@ Huawei Options: Common Options: --topic iOS, Android or Huawei topic message -h, --help Show this message - -v, --version Show version + -V, --version Show version ` // usage will print out the flag options for the server. @@ -417,7 +461,7 @@ func usage() { // handles pinging the endpoint and returns an error if the // agent is in an unhealthy state. -func pinger() error { +func pinger(cfg *config.ConfYaml) error { transport := &http.Transport{ Dial: (&net.Dialer{ Timeout: 5 * time.Second, @@ -428,7 +472,7 @@ func pinger() error { Timeout: time.Second * 10, Transport: transport, } - resp, err := client.Get("http://localhost:" + gorush.PushConf.Core.Port + gorush.PushConf.API.HealthURI) + resp, err := client.Get("http://localhost:" + cfg.Core.Port + cfg.API.HealthURI) if err != nil { return err } @@ -439,26 +483,26 @@ func pinger() error { return nil } -func createPIDFile() error { - if !gorush.PushConf.Core.PID.Enabled { +func createPIDFile(cfg *config.ConfYaml) error { + if !cfg.Core.PID.Enabled { return nil } - pidPath := gorush.PushConf.Core.PID.Path + pidPath := cfg.Core.PID.Path _, err := os.Stat(pidPath) - if os.IsNotExist(err) || gorush.PushConf.Core.PID.Override { + if os.IsNotExist(err) || cfg.Core.PID.Override { currentPid := os.Getpid() if err := os.MkdirAll(filepath.Dir(pidPath), os.ModePerm); err != nil { - return fmt.Errorf("Can't create PID folder on %v", err) + return fmt.Errorf("can't create PID folder on %v", err) } file, err := os.Create(pidPath) if err != nil { - return fmt.Errorf("Can't create PID file: %v", err) + return fmt.Errorf("can't create PID file: %v", err) } defer file.Close() if _, err := file.WriteString(strconv.FormatInt(int64(currentPid), 10)); err != nil { - return fmt.Errorf("Can't write PID information on %s: %v", pidPath, err) + return fmt.Errorf("can't write PID information on %s: %v", pidPath, err) } } else { return fmt.Errorf("%s already exists", pidPath) diff --git a/gorush/metrics.go b/metric/metrics.go similarity index 79% rename from gorush/metrics.go rename to metric/metrics.go index 0f4705390..e717ef043 100644 --- a/gorush/metrics.go +++ b/metric/metrics.go @@ -1,6 +1,8 @@ -package gorush +package metric import ( + "github.com/appleboy/gorush/status" + "github.com/prometheus/client_golang/prometheus" ) @@ -17,11 +19,14 @@ type Metrics struct { HuaweiSuccess *prometheus.Desc HuaweiError *prometheus.Desc QueueUsage *prometheus.Desc + GetQueueUsage func() int } +var getGetQueueUsage = func() int { return 0 } + // NewMetrics returns a new Metrics with all prometheus.Desc initialized -func NewMetrics() Metrics { - return Metrics{ +func NewMetrics(c ...func() int) Metrics { + m := Metrics{ TotalPushCount: prometheus.NewDesc( namespace+"total_push_count", "Number of push count", @@ -62,7 +67,14 @@ func NewMetrics() Metrics { "Length of internal queue", nil, nil, ), + GetQueueUsage: getGetQueueUsage, } + + if len(c) > 0 { + m.GetQueueUsage = c[0] + } + + return m } // Describe returns all possible prometheus.Desc @@ -82,41 +94,41 @@ func (c Metrics) Collect(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric( c.TotalPushCount, prometheus.CounterValue, - float64(StatStorage.GetTotalCount()), + float64(status.StatStorage.GetTotalCount()), ) ch <- prometheus.MustNewConstMetric( c.IosSuccess, prometheus.CounterValue, - float64(StatStorage.GetIosSuccess()), + float64(status.StatStorage.GetIosSuccess()), ) ch <- prometheus.MustNewConstMetric( c.IosError, prometheus.CounterValue, - float64(StatStorage.GetIosError()), + float64(status.StatStorage.GetIosError()), ) ch <- prometheus.MustNewConstMetric( c.AndroidSuccess, prometheus.CounterValue, - float64(StatStorage.GetAndroidSuccess()), + float64(status.StatStorage.GetAndroidSuccess()), ) ch <- prometheus.MustNewConstMetric( c.AndroidError, prometheus.CounterValue, - float64(StatStorage.GetAndroidError()), + float64(status.StatStorage.GetAndroidError()), ) ch <- prometheus.MustNewConstMetric( c.HuaweiSuccess, prometheus.CounterValue, - float64(StatStorage.GetHuaweiSuccess()), + float64(status.StatStorage.GetHuaweiSuccess()), ) ch <- prometheus.MustNewConstMetric( c.HuaweiError, prometheus.CounterValue, - float64(StatStorage.GetHuaweiError()), + float64(status.StatStorage.GetHuaweiError()), ) ch <- prometheus.MustNewConstMetric( c.QueueUsage, prometheus.GaugeValue, - float64(len(QueueNotification)), + float64(c.GetQueueUsage()), ) } diff --git a/metric/metrics_test.go b/metric/metrics_test.go new file mode 100644 index 000000000..2e6c1b2c9 --- /dev/null +++ b/metric/metrics_test.go @@ -0,0 +1,15 @@ +package metric + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMetrics(t *testing.T) { + m := NewMetrics() + assert.Equal(t, 0, m.GetQueueUsage()) + + m = NewMetrics(func() int { return 1 }) + assert.Equal(t, 1, m.GetQueueUsage()) +} diff --git a/netlify.toml b/netlify.toml index d5e45397b..7eaa48c5a 100644 --- a/netlify.toml +++ b/netlify.toml @@ -3,6 +3,7 @@ functions = "release/linux/lambda" [build.environment] + GO_VERSION = "1.16" GO_IMPORT_PATH = "github.com/appleboy/gorush" GO111MODULE = "on" diff --git a/gorush/feedback.go b/notify/feedback.go similarity index 81% rename from gorush/feedback.go rename to notify/feedback.go index 0eb2557e5..c4f873e35 100644 --- a/gorush/feedback.go +++ b/notify/feedback.go @@ -1,18 +1,19 @@ -package gorush +package notify import ( "bytes" - "encoding/json" "errors" "net" "net/http" "time" + + "github.com/appleboy/gorush/logx" ) // DispatchFeedback sends a feedback to the configured gateway. -func DispatchFeedback(log LogPushEntry, url string, timeout int64) error { +func DispatchFeedback(log logx.LogPushEntry, url string, timeout int64) error { if url == "" { - return errors.New("The url can't be empty") + return errors.New("url can't be empty") } payload, err := json.Marshal(log) diff --git a/gorush/feedback_test.go b/notify/feedback_test.go similarity index 63% rename from gorush/feedback_test.go rename to notify/feedback_test.go index 4a3ebd8de..ec20b502e 100644 --- a/gorush/feedback_test.go +++ b/notify/feedback_test.go @@ -1,4 +1,4 @@ -package gorush +package notify import ( "log" @@ -7,13 +7,14 @@ import ( "testing" "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" "github.com/stretchr/testify/assert" ) func TestEmptyFeedbackURL(t *testing.T) { - // PushConf, _ = config.LoadConf("") - logEntry := LogPushEntry{ + cfg, _ := config.LoadConf() + logEntry := logx.LogPushEntry{ ID: "", Type: "", Platform: "", @@ -22,14 +23,14 @@ func TestEmptyFeedbackURL(t *testing.T) { Error: "", } - err := DispatchFeedback(logEntry, PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout) + err := DispatchFeedback(logEntry, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) assert.NotNil(t, err) } func TestHTTPErrorInFeedbackCall(t *testing.T) { - config, _ := config.LoadConf("") - config.Core.FeedbackURL = "http://test.example.com/api/" - logEntry := LogPushEntry{ + cfg, _ := config.LoadConf() + cfg.Core.FeedbackURL = "http://test.example.com/api/" + logEntry := logx.LogPushEntry{ ID: "", Type: "", Platform: "", @@ -38,7 +39,7 @@ func TestHTTPErrorInFeedbackCall(t *testing.T) { Error: "", } - err := DispatchFeedback(logEntry, config.Core.FeedbackURL, config.Core.FeedbackTimeout) + err := DispatchFeedback(logEntry, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) assert.NotNil(t, err) } @@ -58,9 +59,9 @@ func TestSuccessfulFeedbackCall(t *testing.T) { ) defer httpMock.Close() - config, _ := config.LoadConf("") - config.Core.FeedbackURL = httpMock.URL - logEntry := LogPushEntry{ + cfg, _ := config.LoadConf() + cfg.Core.FeedbackURL = httpMock.URL + logEntry := logx.LogPushEntry{ ID: "", Type: "", Platform: "", @@ -69,6 +70,6 @@ func TestSuccessfulFeedbackCall(t *testing.T) { Error: "", } - err := DispatchFeedback(logEntry, config.Core.FeedbackURL, config.Core.FeedbackTimeout) + err := DispatchFeedback(logEntry, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) assert.Nil(t, err) } diff --git a/notify/global.go b/notify/global.go new file mode 100644 index 000000000..7433be6b5 --- /dev/null +++ b/notify/global.go @@ -0,0 +1,18 @@ +package notify + +import ( + "github.com/appleboy/go-fcm" + "github.com/msalihkarakasli/go-hms-push/push/core" + "github.com/sideshow/apns2" +) + +var ( + // ApnsClient is apns client + ApnsClient *apns2.Client + // FCMClient is apns client + FCMClient *fcm.Client + // HMSClient is Huawei push client + HMSClient *core.HMSClient + // MaxConcurrentIOSPushes pool to limit the number of concurrent iOS pushes + MaxConcurrentIOSPushes chan struct{} +) diff --git a/notify/main_test.go b/notify/main_test.go new file mode 100644 index 000000000..899ef0b55 --- /dev/null +++ b/notify/main_test.go @@ -0,0 +1,19 @@ +package notify + +import ( + "log" + "os" + "testing" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/status" +) + +func TestMain(m *testing.M) { + cfg, _ := config.LoadConf() + if err := status.InitAppStatus(cfg); err != nil { + log.Fatal(err) + } + + os.Exit(m.Run()) +} diff --git a/gorush/notification.go b/notify/notification.go similarity index 70% rename from gorush/notification.go rename to notify/notification.go index 87ec42c31..a5367d9d7 100644 --- a/gorush/notification.go +++ b/notify/notification.go @@ -1,17 +1,25 @@ -package gorush +package notify import ( + "context" "errors" "net/http" "net/url" "os" "strings" - "sync" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" "github.com/appleboy/go-fcm" + "github.com/golang-queue/queue" + jsoniter "github.com/json-iterator/go" "github.com/msalihkarakasli/go-hms-push/push/model" ) +var json = jsoniter.ConfigCompatibleWithStandardLibrary + // D provide string array type D map[string]interface{} @@ -50,11 +58,13 @@ type RequestPush struct { Notifications []PushNotification `json:"notifications" binding:"required"` } +// ResponsePush response of notification request. +type ResponsePush struct { + Logs []logx.LogPushEntry `json:"logs"` +} + // PushNotification is single notification request type PushNotification struct { - wg *sync.WaitGroup - log *[]LogPushEntry - // Common ID string `json:"notif_id,omitempty"` Tokens []string `json:"tokens" binding:"required"` @@ -108,35 +118,23 @@ type PushNotification struct { Apns D `json:"apns,omitempty"` } -// WaitDone decrements the WaitGroup counter. -func (p *PushNotification) WaitDone() { - if p.wg != nil { - p.wg.Done() - } -} - -// AddWaitCount increments the WaitGroup counter. -func (p *PushNotification) AddWaitCount() { - if p.wg != nil { - p.wg.Add(1) - } -} - -// AddLog record fail log of notification -func (p *PushNotification) AddLog(log LogPushEntry) { - if p.log != nil { - *p.log = append(*p.log, log) +// Bytes for queue message +func (p *PushNotification) Bytes() []byte { + b, err := json.Marshal(p) + if err != nil { + panic(err) } + return b } // IsTopic check if message format is topic for FCM // ref: https://firebase.google.com/docs/cloud-messaging/send-message#topic-http-post-request func (p *PushNotification) IsTopic() bool { - if p.Platform == PlatFormAndroid { + if p.Platform == core.PlatFormAndroid { return p.To != "" && strings.HasPrefix(p.To, "/topics/") || p.Condition != "" } - if p.Platform == PlatFormHuawei { + if p.Platform == core.PlatFormHuawei { return p.Topic != "" || p.Condition != "" } @@ -144,39 +142,39 @@ func (p *PushNotification) IsTopic() bool { } // CheckMessage for check request message -func CheckMessage(req PushNotification) error { +func CheckMessage(req *PushNotification) error { var msg string // ignore send topic mesaage from FCM if !req.IsTopic() && len(req.Tokens) == 0 && req.To == "" { msg = "the message must specify at least one registration ID" - LogAccess.Debug(msg) + logx.LogAccess.Debug(msg) return errors.New(msg) } - if len(req.Tokens) == PlatFormIos && req.Tokens[0] == "" { + if len(req.Tokens) == core.PlatFormIos && req.Tokens[0] == "" { msg = "the token must not be empty" - LogAccess.Debug(msg) + logx.LogAccess.Debug(msg) return errors.New(msg) } - if req.Platform == PlatFormAndroid && len(req.Tokens) > 1000 { + if req.Platform == core.PlatFormAndroid && len(req.Tokens) > 1000 { msg = "the message may specify at most 1000 registration IDs" - LogAccess.Debug(msg) + logx.LogAccess.Debug(msg) return errors.New(msg) } - if req.Platform == PlatFormHuawei && len(req.Tokens) > 500 { + if req.Platform == core.PlatFormHuawei && len(req.Tokens) > 500 { msg = "the message may specify at most 500 registration IDs for Huawei" - LogAccess.Debug(msg) + logx.LogAccess.Debug(msg) return errors.New(msg) } // ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref - if req.Platform == PlatFormAndroid && req.TimeToLive != nil && *req.TimeToLive > uint(2419200) { + if req.Platform == core.PlatFormAndroid && req.TimeToLive != nil && *req.TimeToLive > uint(2419200) { msg = "the message's TimeToLive field must be an integer " + "between 0 and 2419200 (4 weeks)" - LogAccess.Debug(msg) + logx.LogAccess.Debug(msg) return errors.New(msg) } @@ -191,45 +189,83 @@ func SetProxy(proxy string) error { } http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(proxyURL)} - LogAccess.Debug("Set http proxy as " + proxy) + logx.LogAccess.Debug("Set http proxy as " + proxy) return nil } // CheckPushConf provide check your yml config. -func CheckPushConf() error { - if !PushConf.Ios.Enabled && !PushConf.Android.Enabled && !PushConf.Huawei.Enabled { - return errors.New("Please enable iOS, Android or Huawei config in yml config") +func CheckPushConf(cfg *config.ConfYaml) error { + if !cfg.Ios.Enabled && !cfg.Android.Enabled && !cfg.Huawei.Enabled { + return errors.New("please enable iOS, Android or Huawei config in yml config") } - if PushConf.Ios.Enabled { - if PushConf.Ios.KeyPath == "" && PushConf.Ios.KeyBase64 == "" { - return errors.New("Missing iOS certificate key") + if cfg.Ios.Enabled { + if cfg.Ios.KeyPath == "" && cfg.Ios.KeyBase64 == "" { + return errors.New("missing iOS certificate key") } // check certificate file exist - if PushConf.Ios.KeyPath != "" { - if _, err := os.Stat(PushConf.Ios.KeyPath); os.IsNotExist(err) { + if cfg.Ios.KeyPath != "" { + if _, err := os.Stat(cfg.Ios.KeyPath); os.IsNotExist(err) { return errors.New("certificate file does not exist") } } } - if PushConf.Android.Enabled { - if PushConf.Android.APIKey == "" { + if cfg.Android.Enabled { + if cfg.Android.APIKey == "" { return errors.New("Missing Android API Key") } } - if PushConf.Huawei.Enabled { - if PushConf.Huawei.AppSecret == "" { + if cfg.Huawei.Enabled { + if cfg.Huawei.AppSecret == "" { return errors.New("Missing Huawei App Secret") } - if PushConf.Huawei.AppID == "" { + if cfg.Huawei.AppID == "" { return errors.New("Missing Huawei App ID") } } return nil } + +// SendNotification send notification +func SendNotification(req queue.QueuedMessage, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + v, ok := req.(*PushNotification) + if !ok { + if err = json.Unmarshal(req.Bytes(), &v); err != nil { + return + } + } + + switch v.Platform { + case core.PlatFormIos: + resp, err = PushToIOS(v, cfg) + case core.PlatFormAndroid: + resp, err = PushToAndroid(v, cfg) + case core.PlatFormHuawei: + resp, err = PushToHuawei(v, cfg) + } + + if cfg.Core.FeedbackURL != "" { + for _, l := range resp.Logs { + err := DispatchFeedback(l, cfg.Core.FeedbackURL, cfg.Core.FeedbackTimeout) + if err != nil { + logx.LogError.Error(err) + } + } + } + + return +} + +// Run send notification +var Run = func(cfg *config.ConfYaml) func(ctx context.Context, msg queue.QueuedMessage) error { + return func(ctx context.Context, msg queue.QueuedMessage) error { + _, err := SendNotification(msg, cfg) + return err + } +} diff --git a/gorush/notification_apns.go b/notify/notification_apns.go similarity index 72% rename from gorush/notification_apns.go rename to notify/notification_apns.go index f7f057d13..143cb4f0c 100644 --- a/gorush/notification_apns.go +++ b/notify/notification_apns.go @@ -1,9 +1,10 @@ -package gorush +package notify import ( "crypto/ecdsa" "crypto/tls" "encoding/base64" + "encoding/json" "errors" "net" "net/http" @@ -11,12 +12,16 @@ import ( "sync" "time" + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + "github.com/mitchellh/mapstructure" "github.com/sideshow/apns2" "github.com/sideshow/apns2/certificate" "github.com/sideshow/apns2/payload" "github.com/sideshow/apns2/token" - "github.com/sirupsen/logrus" "golang.org/x/net/http2" ) @@ -26,6 +31,8 @@ var ( tcpKeepAlive = 60 * time.Second ) +var doOnce sync.Once + // DialTLS is the default dial function for creating TLS connections for // non-proxied HTTPS requests. var DialTLS = func(cfg *tls.Config) func(network, addr string) (net.Conn, error) { @@ -46,45 +53,45 @@ type Sound struct { } // InitAPNSClient use for initialize APNs Client. -func InitAPNSClient() error { - if PushConf.Ios.Enabled { +func InitAPNSClient(cfg *config.ConfYaml) error { + if cfg.Ios.Enabled { var err error var authKey *ecdsa.PrivateKey var certificateKey tls.Certificate var ext string - if PushConf.Ios.KeyPath != "" { - ext = filepath.Ext(PushConf.Ios.KeyPath) + if cfg.Ios.KeyPath != "" { + ext = filepath.Ext(cfg.Ios.KeyPath) switch ext { case ".p12": - certificateKey, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password) + certificateKey, err = certificate.FromP12File(cfg.Ios.KeyPath, cfg.Ios.Password) case ".pem": - certificateKey, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password) + certificateKey, err = certificate.FromPemFile(cfg.Ios.KeyPath, cfg.Ios.Password) case ".p8": - authKey, err = token.AuthKeyFromFile(PushConf.Ios.KeyPath) + authKey, err = token.AuthKeyFromFile(cfg.Ios.KeyPath) default: err = errors.New("wrong certificate key extension") } if err != nil { - LogError.Error("Cert Error:", err.Error()) + logx.LogError.Error("Cert Error:", err.Error()) return err } - } else if PushConf.Ios.KeyBase64 != "" { - ext = "." + PushConf.Ios.KeyType - key, err := base64.StdEncoding.DecodeString(PushConf.Ios.KeyBase64) + } else if cfg.Ios.KeyBase64 != "" { + ext = "." + cfg.Ios.KeyType + key, err := base64.StdEncoding.DecodeString(cfg.Ios.KeyBase64) if err != nil { - LogError.Error("base64 decode error:", err.Error()) + logx.LogError.Error("base64 decode error:", err.Error()) return err } switch ext { case ".p12": - certificateKey, err = certificate.FromP12Bytes(key, PushConf.Ios.Password) + certificateKey, err = certificate.FromP12Bytes(key, cfg.Ios.Password) case ".pem": - certificateKey, err = certificate.FromPemBytes(key, PushConf.Ios.Password) + certificateKey, err = certificate.FromPemBytes(key, cfg.Ios.Password) case ".p8": authKey, err = token.AuthKeyFromBytes(key) default: @@ -92,29 +99,29 @@ func InitAPNSClient() error { } if err != nil { - LogError.Error("Cert Error:", err.Error()) + logx.LogError.Error("Cert Error:", err.Error()) return err } } if ext == ".p8" { - if PushConf.Ios.KeyID == "" || PushConf.Ios.TeamID == "" { + if cfg.Ios.KeyID == "" || cfg.Ios.TeamID == "" { msg := "You should provide ios.KeyID and ios.TeamID for P8 token" - LogError.Error(msg) + logx.LogError.Error(msg) return errors.New(msg) } token := &token.Token{ AuthKey: authKey, // KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) - KeyID: PushConf.Ios.KeyID, + KeyID: cfg.Ios.KeyID, // TeamID from developer account (View Account -> Membership) - TeamID: PushConf.Ios.TeamID, + TeamID: cfg.Ios.TeamID, } - ApnsClient, err = newApnsTokenClient(token) + ApnsClient, err = newApnsTokenClient(cfg, token) } else { - ApnsClient, err = newApnsClient(certificateKey) + ApnsClient, err = newApnsClient(cfg, certificateKey) } if h2Transport, ok := ApnsClient.HTTPClient.Transport.(*http2.Transport); ok { @@ -122,25 +129,29 @@ func InitAPNSClient() error { } if err != nil { - LogError.Error("Transport Error:", err.Error()) + logx.LogError.Error("Transport Error:", err.Error()) return err } + + doOnce.Do(func() { + MaxConcurrentIOSPushes = make(chan struct{}, cfg.Ios.MaxConcurrentPushes) + }) } return nil } -func newApnsClient(certificate tls.Certificate) (*apns2.Client, error) { +func newApnsClient(cfg *config.ConfYaml, certificate tls.Certificate) (*apns2.Client, error) { var client *apns2.Client - if PushConf.Ios.Production { + if cfg.Ios.Production { client = apns2.NewClient(certificate).Production() } else { client = apns2.NewClient(certificate).Development() } - if PushConf.Core.HTTPProxy == "" { + if cfg.Core.HTTPProxy == "" { return client, nil } @@ -171,16 +182,16 @@ func newApnsClient(certificate tls.Certificate) (*apns2.Client, error) { return client, nil } -func newApnsTokenClient(token *token.Token) (*apns2.Client, error) { +func newApnsTokenClient(cfg *config.ConfYaml, token *token.Token) (*apns2.Client, error) { var client *apns2.Client - if PushConf.Ios.Production { + if cfg.Ios.Production { client = apns2.NewTokenClient(token).Production() } else { client = apns2.NewTokenClient(token).Development() } - if PushConf.Core.HTTPProxy == "" { + if cfg.Core.HTTPProxy == "" { return client, nil } @@ -207,7 +218,7 @@ func configureHTTP2ConnHealthCheck(h2Transport *http2.Transport) { h2Transport.PingTimeout = 1 * time.Second } -func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload.Payload { +func iosAlertDictionary(payload *payload.Payload, req *PushNotification) *payload.Payload { // Alert dictionary if len(req.Title) > 0 { @@ -276,9 +287,9 @@ func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload } // GetIOSNotification use for define iOS notification. -// The iOS Notification Payload -// ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1 -func GetIOSNotification(req PushNotification) *apns2.Notification { +// The iOS Notification Payload (Payload Key Reference) +// Ref: https://apple.co/2VtH6Iu +func GetIOSNotification(req *PushNotification) (*apns2.Notification, error) { notification := &apns2.Notification{ ApnsID: req.ApnsID, Topic: req.Topic, @@ -358,42 +369,54 @@ func GetIOSNotification(req PushNotification) *apns2.Notification { notification.Payload = payload - return notification + jsonMarshall, err := json.Marshal(notification) + if err != nil { + LogError.Error("Failed to marshal the default message! Error is " + err.Error()) + return nil, err + } + + LogAccess.Debugf("Default message is %s", string(jsonMarshall)) + + return notification, nil } -func getApnsClient(req PushNotification) (client *apns2.Client) { - if req.Production { +func getApnsClient(cfg *config.ConfYaml, req *PushNotification) (client *apns2.Client) { + switch { + case req.Production: client = ApnsClient.Production() - } else if req.Development { + case req.Development: client = ApnsClient.Development() - } else { - if PushConf.Ios.Production { + default: + if cfg.Ios.Production { client = ApnsClient.Production() } else { client = ApnsClient.Development() } } + return } // PushToIOS provide send notification to APNs server. -func PushToIOS(req PushNotification) { - LogAccess.Debug("Start push notification for iOS") +func PushToIOS(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for iOS") var ( retryCount = 0 - maxRetry = PushConf.Ios.MaxRetry + maxRetry = cfg.Ios.MaxRetry ) if req.Retry > 0 && req.Retry < maxRetry { maxRetry = req.Retry } + resp = &ResponsePush{} + Retry: var newTokens []string - notification := GetIOSNotification(req) - client := getApnsClient(req) + notification, _ := GetIOSNotification(req) + client := getApnsClient(cfg, req) var wg sync.WaitGroup for _, token := range req.Tokens { @@ -411,37 +434,30 @@ Retry: // ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65 err = errors.New(res.Reason) } + // apns server error - LogPush(FailedPush, token, req, err) - - if PushConf.Core.Sync { - req.AddLog(getLogPushEntry(FailedPush, token, req, err)) - } else if PushConf.Core.FeedbackURL != "" { - go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) { - err := DispatchFeedback(log, url, timeout) - if err != nil { - logger.Error(err) - } - }(LogError, getLogPushEntry(FailedPush, token, req, err), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout) - } + errLog := logPush(cfg, core.FailedPush, token, req, err) + resp.Logs = append(resp.Logs, errLog) - StatStorage.AddIosError(1) + status.StatStorage.AddIosError(1) // We should retry only "retryable" statuses. More info about response: - // https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns + // See https://apple.co/3AdNane (Handling Notification Responses from APNs) if res != nil && res.StatusCode >= http.StatusInternalServerError { newTokens = append(newTokens, token) } } if res != nil && res.Sent() { - LogPush(SucceededPush, token, req, nil) - StatStorage.AddIosSuccess(1) + logPush(cfg, core.SucceededPush, token, req, nil) + status.StatStorage.AddIosSuccess(1) } + // free push slot <-MaxConcurrentIOSPushes wg.Done() }(*notification, token) } + wg.Wait() if len(newTokens) > 0 && retryCount < maxRetry { @@ -451,4 +467,6 @@ Retry: req.Tokens = newTokens goto Retry } + + return resp, nil } diff --git a/gorush/notification_apns_test.go b/notify/notification_apns_test.go similarity index 52% rename from gorush/notification_apns_test.go rename to notify/notification_apns_test.go index e000133e2..28dc318fe 100644 --- a/gorush/notification_apns_test.go +++ b/notify/notification_apns_test.go @@ -1,53 +1,54 @@ -package gorush +package notify import ( - "context" - "encoding/json" "log" "net/http" "net/url" - "os" "testing" "time" "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/status" "github.com/buger/jsonparser" "github.com/sideshow/apns2" "github.com/stretchr/testify/assert" ) -const certificateValidP12 = `MIIKlgIBAzCCClwGCSqGSIb3DQEHAaCCCk0EggpJMIIKRTCCBMcGCSqGSIb3DQEHBqCCBLgwggS0AgEAMIIErQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQID/GJtcRhjvwCAggAgIIEgE5ralQoQBDgHgdp5+EwBaMjcZEJUXmYRdVCttIwfN2OxlIs54tob3/wpUyWGqJ+UXy9X+4EsWpDPUfTN/w88GMgj0kftpTqG0+3Hu/9pkZO4pdLCiyMGOJnXCOdhHFirtTXAR3QvnKKIpXIKrmZ4rcr/24Uvd/u669Tz8VDgcGOQazKeyvtdW7TJBxMFRv+IsQi/qCj5PkQ0jBbZ1LAc4C8mCMwOcH+gi/e471mzPWihQmynH2yJlZ4jb+taxQ/b8Dhlni2vcIMn+HknRk3Cyo8jfFvvO0BjvVvEAPxPJt7X96VFFS2KlyXjY3zt0siGrzQpczgPB/1vTqhQUvoOBw6kcXWgOjwt+gR8Mmo2DELnQqGhbYuWu52doLgVvD+zGr5vLYXHz6gAXnI6FVyHb+oABeBet3cer3EzGR7r+VoLmWSBm8SyRHwi0mxE63S7oD1j22jaTo7jnQBFZaY+cPaATcFjqW67x4j8kXh9NRPoINSgodLJrgmet2D1iOKuLTkCWf0UTi2HUkn9Zf0y+IIViZaVE4mWaGb9xTBClfa4KwM5gSz3jybksFKbtnzzPFuzClu+2mdthJs/58Ao40eyaykNmzSPhDv1F8Mai8bfaAqSdcBl5ZB2PF33xhuNSS4j2uIh1ICGv9DueyN507iEMQO2yCcaQTMKejV7/52h9LReS5/QPXDJhWMVpTb5FGCP7EmO0lZTeBNO5MlDzDQfz5xcFqHqfoby2sfAMU8HNB8wzdcwHtacgKGLBjLkapxyTsqYE5Kry6UxclvF4soR8TZoQ69E7WsKZLmTaw2+msmnDJubpY0NqkRqkVk7umtVC0D+w6AIKDrY58HMlm80/ImgGXwybA1kuZMxqMzaH/xFiAHOSIGuVPtGgGFYNEdGbfOryuhFo9l1nSECWm8MN9hYwB1Rn9p6rkd+zrvbU1zv13drtrZ/vL0NlT02tlkS8NdWLGJkZhWgc2c89GyRb7mjuHRHu/BWGED3y7vjHo/lnkPsLJXw0ovIlqhtW0BtN/xSpGg0phDbn0Et5jb7Xmc+fWimgbtIUHcnJOV5QSYFzlR+kbzx0oKRARU4B3CWkdPeaXkrmw0IriS6vOdZcM8YBJ6BtXEDLsrSH7tHxeknYHLEl0uy9Oc1+Huyrz8j7Zxo8SQj9H+RX0HeMl8YB3HUBLHYcqCEBjm7mHI4rP8ULVkC5oCA5w3tJfMyvS/jZRiwMUyr0tiWhrh/AM3wPPX54cqozefojWKrqGtK9I+n0cfwW9rU3FsUcpMTo9uQ27O7NejKP2X/LLMZkQvWUEabZNjNrWsbp6d51/frfIR7kRlZAmmt2yS23h6w6RvKTAVUrNatEyzokfNAIDml6lYLweNJATZU08BznhPpuvh3bKOSos5uaJBYpsOYexoMGnAig428qypw0cmv6sCjO/xdIL86COVNQp/UtjcXJ9/E0bnVmzfpgA3WCy+29YXPx7DZ1U+bQ9jOO/P9pwqLwTH+gpcZiVm3ru1Tmiq6iZ8cG7tMLfTBNXljvtlDzCCBXYGCSqGSIb3DQEHAaCCBWcEggVjMIIFXzCCBVsGCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAgCvAo2HCM89AICCAAEggTIOcfaF6qWYXlo+BNBjYIllg0VwQSJXZmcqj2vXlDPIPrTuQ+QDmGnhYR6hVbcMrk3o7eQhH3ThyHM+KEzkYx1IAYCOdEQXYcFguoDG1CxHrgE1Y0H8yndc/yPw2tqkx6X9ZemdYp3welXZjYgUi9MKvGbN6lZ0cFTU+2+0+H/IyKQ3OUjDNymhOxypOPBaK2eQsJ7XumgJ6nLvNZDRx/f277J+LD/z0pOhzUOljhvA3dkBMpEvomX4erZihErunqP1jbH9O3eIYq9J7czGS2xuckolW19KqWOyWh8KRI/LnAqiEh2e0hZ7lpltj79PenO66VGPbn2f85A6b6PD4kipgoMB2IRibkoodyn/oo3WizO386fqtEfUlbFmxI4y4utobWe7nZ2VuBLgA/mgyyxqAJK1erM98NDWB/Njo1CPsaMl9ubXKPOyIZG0fOLUa23DfkJUEiCb839yKc2oEJkI0wtrvbeh1TAPv4vL4TxiXdiJ/6YrSa0/FQh6nqk1jiK+p22MzvEIkDOyPqk/GsAlc/k2kQ/M86tF50wtc08wnXv8+G8k6qTZ7VCluffzAUt64La47qj8XIfh7tKleznzQSbyjlNX8DsFVzGbCg9G4PKxrLAVnKEgIK1kOopSF1UUMqSKE0D3s5AURQhX8/Cf9h+WtNsWK+y7EMOntsBc2op0M7fQ9Jm73NF7CCYeqb0W7sziJSzqJsJgNp0+ArAcZQExeltxAb6kye3Z5JtP/oaB+jmcHKy9l/nhzKA3MzJwCZ5Q3oviPlNqJvFVBmGEEvC6iULLuv6VSxNdB2uH3Tsfa1TMOOHOadBTcyWatjscYS9ynkXuw1+8+FvEu3EV0UwopZmlSaYfMKQ2jshT4Cgg1zy15uKjomojtAaaF+D/U6KZVQk/7rzdaDmvkJvNtc5n9BW96tmrOhI6L+/WihS570qaitQUsHBBTOetlHXYEPiOkH8BhjzNHXLH9YpC8OEQOhO+1jEninDKNdbU7SCqV0+YE6kfR5Bfkw2MxoIQLtUnHjK6GR/q3fxo1TirbTe8c8dp907wgcXkT/rONX/iG1JTjxV2ixR1oM68LYI3eJzY801/xBSnmOjdzOPUHXCNHDTf9kPjkOtZWkGbZugf4ckRH/L8dK2Vo4QpFUN8AZjomanzLxjQZ+DVFNoPDT2K+0pezsMiwSJlyBGoIQHN0/2zVNVLo/KfARIOac1iC8+duj5S/1c52+PvP7FkMe72QUV0KUQ7AJHXUvQtFZx4Ny579/B/3c4D72CFSydhw3/+nL9+Nz956UafZ6G7HZ96frMTgajMcXQe1uXwgN2iTnnNtLdcC/ARHS1RkjgXHohO+VGuQxOo23PPABVaxex2SGGXX7Fc4MI2Xr4uaimZIzcUkuHUnhZQGkcFlVekZ/wJXookq0Fv8DuPuv7mGCx6BKERU9I+NMU6xLNe6VsfkS8t5uVq1EIINnddGl9VGpqOPN8EgU47gh6CcDkP8sxXsT8pZ1vQyJrUlWGYp68/okoQ+7lqnd06wzVDIwAE/+pq9PUxLdNvYE0sNe4JrEcKO0xp/zxCqLjHLT+rB896v2OsU0BA5tPQA7xkKp4PuQr6qO8fTVyfhImVmoFX6b9VgtLHIlJMVowIwYJKoZIhvcNAQkVMRYEFIwanwBmvSRCuV0e6/5ei8oEPXODMDMGCSqGSIb3DQEJFDEmHiQAQQBQAE4AUwAvADIAIABQAHIAaQB2AGEAdABlACAASwBlAHkwMTAhMAkGBSsOAwIaBQAEFK7XWCbKGSKmxNqE2E8dmCfwhaQxBAjPcbkv12ro6gICCAA=` - -const certificateValidPEM = `QmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IDhDIDFBIDlGIDAwIDY2IEJEIDI0IDQyIEI5IDVEIDFFIEVCIEZFIDVFIDhCIENBIDA0IDNEIDczIDgzIAogICAgZnJpZW5kbHlOYW1lOiBBUE5TLzIgUHJpdmF0ZSBLZXkKc3ViamVjdD0vQz1OWi9TVD1XZWxsaW5ndG9uL0w9V2VsbGluZ3Rvbi9PPUludGVybmV0IFdpZGdpdHMgUHR5IEx0ZC9PVT05WkVINjJLUlZWL0NOPUFQTlMvMiBEZXZlbG9wbWVudCBJT1MgUHVzaCBTZXJ2aWNlczogY29tLnNpZGVzaG93LkFwbnMyCmlzc3Vlcj0vQz1OWi9TVD1XZWxsaW5ndG9uL0w9V2VsbGluZ3Rvbi9PPUFQTlMvMiBJbmMuL09VPUFQTlMvMiBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucy9DTj1BUE5TLzIgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ2ekNDQXRNQ0FRSXdEUVlKS29aSWh2Y05BUUVMQlFBd2djTXhDekFKQmdOVkJBWVRBazVhTVJNd0VRWUQKVlFRSUV3cFhaV3hzYVc1bmRHOXVNUk13RVFZRFZRUUhFd3BYWld4c2FXNW5kRzl1TVJRd0VnWURWUVFLRXd0QgpVRTVUTHpJZ1NXNWpMakV0TUNzR0ExVUVDeE1rUVZCT1V5OHlJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnClVtVnNZWFJwYjI1ek1VVXdRd1lEVlFRREV6eEJVRTVUTHpJZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlMKWld4aGRHbHZibk1nUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3dIaGNOTVRZd01UQTRNRGd6TkRNdwpXaGNOTWpZd01UQTFNRGd6TkRNd1dqQ0JzakVMTUFrR0ExVUVCaE1DVGxveEV6QVJCZ05WQkFnVENsZGxiR3hwCmJtZDBiMjR4RXpBUkJnTlZCQWNUQ2xkbGJHeHBibWQwYjI0eElUQWZCZ05WQkFvVEdFbHVkR1Z5Ym1WMElGZHAKWkdkcGRITWdVSFI1SUV4MFpERVRNQkVHQTFVRUN4TUtPVnBGU0RZeVMxSldWakZCTUQ4R0ExVUVBeE00UVZCTwpVeTh5SUVSbGRtVnNiM0J0Wlc1MElFbFBVeUJRZFhOb0lGTmxjblpwWTJWek9pQmpiMjB1YzJsa1pYTm9iM2N1ClFYQnVjekl3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFkwYzFUS0I1b1pQd1EKN3QxQ3dNSXJ2cUI2R0lVM3RQeTZSaGNrWlhUa09COFllQldKN1VLZkN6OEhHSEZWb21CUDBUNU9VYmVxUXpxVwpZSmJRelo4YTZaTXN6YkwwbE80WDkrKzNPaTUvVHRBd09VT0s4ck9GTjI1bTJLZnNheUhRWi80dldTdEsyRndtCjVhSmJHTGxwSC9iLzd6MUQ0dmhtTWdvQnVUMUl1eWhHaXlGeGxaOUV0VGxvRnZzcU0xRTVmWVpPU1pBQ3lYVGEKSzR2ZGdiUU1nVVZzSTcxNEZBZ0xUbEswVWVpUmttS20zcGRidGZWYnJ0aHpJK0lIWEtJdFVJeStGbjIwUFJNaApkU25henRTejd0Z0JXQ0l4MjJxdmNZb2dIV2lPZ1VZSU03NzJ6RTJ5OFVWT3I4RHNpUmxzT0hTQTdFSTRNSmNRCkcyRlVxMlovQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBR3lmeU8ySE1nY2RlQmN6M2J0NUJJTFgKZjdSQTIvVW1WSXdjS1IxcW90VHNGK1BuQm1jSUxleU9RZ0RlOXRHVTVjUmM3OWtEdDNKUm1NWVJPRklNZ0ZSZgpXZjIydU9LdGhvN0dRUWFLdkcrYmtnTVZkWUZSbEJIbkYrS2VxS0g4MXFiOXArQ1Q0SXcwR2VoSUwxRGlqRkxSClZJQUlCWXB6NG9CUENJRTFJU1ZUK0ZnYWYzSkFoNTlrYlBiTnc5QUlEeGFCdFA4RXV6U1ROd2ZieG9HYkNvYlMKV2kxVThJc0N3UUZ0OHRNMW00WlhEMUNjWklyR2RyeWVBaFZrdktJSlJpVTVRWVdJMm5xWk4rSnFRdWNtOWFkMAptWU81bUprSW9iVWE0K1pKaENQS0VkbWdwRmJSR2swd1Z1YURNOUN2NlAyc3JzWUFqYU80eTNWUDBHdk5LUkk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KQmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IDhDIDFBIDlGIDAwIDY2IEJEIDI0IDQyIEI5IDVEIDFFIEVCIEZFIDVFIDhCIENBIDA0IDNEIDczIDgzIAogICAgZnJpZW5kbHlOYW1lOiBBUE5TLzIgUHJpdmF0ZSBLZXkKS2V5IEF0dHJpYnV0ZXM6IDxObyBBdHRyaWJ1dGVzPgotLS0tLUJFR0lOIFJTQSBQUklWQVRFIEtFWS0tLS0tCk1JSUVvd0lCQUFLQ0FRRUEyTkhOVXlnZWFHVDhFTzdkUXNEQ0s3NmdlaGlGTjdUOHVrWVhKR1YwNURnZkdIZ1YKaWUxQ253cy9CeGh4VmFKZ1Q5RStUbEczcWtNNmxtQ1cwTTJmR3VtVExNMnk5SlR1Ri9mdnR6b3VmMDdRTURsRAppdkt6aFRkdVp0aW43R3NoMEdmK0wxa3JTdGhjSnVXaVd4aTVhUi8yLys4OVErTDRaaklLQWJrOVNMc29Sb3NoCmNaV2ZSTFU1YUJiN0tqTlJPWDJHVGttUUFzbDAyaXVMM1lHMERJRkZiQ085ZUJRSUMwNVN0Rkhva1pKaXB0NlgKVzdYMVc2N1ljeVBpQjF5aUxWQ012aFo5dEQwVElYVXAyczdVcys3WUFWZ2lNZHRxcjNHS0lCMW9qb0ZHQ0RPKwo5c3hOc3ZGRlRxL0E3SWtaYkRoMGdPeENPRENYRUJ0aFZLdG1md0lEQVFBQkFvSUJBUUNXOFpDSStPQWFlMXRFCmlwWjlGMmJXUDNMSExYVG84RllWZENBK1ZXZUlUazNQb2lJVWtKbVYwYVdDVWhEc3RndG81ZG9EZWo1c0NUdXIKWHZqL3luYWVyTWVxSkZZV2tld2p3WmNnTHlBWnZ3dU8xdjdmcDlFMHgvOVRHRGZuampuUE5lYXVuZHhXMGNOdAp6T1kzbDBIVkhzeTlKcGUzUURjQUpvdnk0VHY1K2hGWTRrRHhVQkdzeWp2aFNjVmdLZzV0TGtKY2xtM3NPdS9MCkd5THFwd05JM09KQWRNSXVWRDROMkJaMWFPRWFwNm1wMnk4SWUwL1I0WVdjYVo1QTRQdzd4VVBsNlNYYzl1dWEKLzc4UVRFUnRQQzZlanlDQmlFMDVhOG0zUTNpdWQzWHRubHl3czJLd2hnQkFmRTZNNHpSL2YzT1FCN1pJWE1oeQpacG1aWnc1eEFvR0JBUFluODRJcmxJUWV0V1FmdlBkTTdLemdoNlVESEN1Z25sQ0RnaHdZcFJKR2k4aE1mdVpWCnhOSXJZQUp6TFlEUTAxbEZKUkpnV1hUY2JxejlOQnoxbmhnK2NOT3oxL0tZKzM4ZXVkZWU2RE5ZbXp0UDdqRFAKMmpuYVMrZHRqQzhoQVhPYm5GcUcrTmlsTURMTHU2YVJtckphSW1ialNyZnlMaUU2bXZKN3U4MW5Bb0dCQU9GOQpnOTN3WjBtTDFyazJzNVd3SEdUTlUvSGFPdG1XUzR6N2tBN2Y0UWFSdWIrTXdwcFptbURaUEhwaVpYN0JQY1p6CmlPUFFoK3huN0lxUkdvUVdCTHlrQlZ0OHpaRm9MWkpvQ1IzbjYzbGV4NUE0cC8wUHAxZ0ZaclIreFg4UFlWb3MKM3llZWlXeVBLc1hYTmMwczVRd0haY1g2V2I4RUhUaFRYR0NCZXRjcEFvR0FNZVFKQzlJUGFQUGNhZTJ3M0NMQQpPWTNNa0ZwZ0JFdXFxc0RzeHdzTHNmZVFiMGxwMHYrQlErTzhzdUpyVDVlRHJxMUFCVWgzK1NLUVlBbDEzWVMrCnhVVXFrdzM1YjljbjZpenRGOUhDV0YzV0lLQmpzNHI5UFFxTXBkeGpORTRwUUNoQytXb3YxNkVyY3JBdVdXVmIKaUZpU2JtNFUvOUZiSGlzRnFxMy9jM01DZ1lCK3Z6U3VQZ0Z3MzcrMG9FRFZ0UVpneXVHU29wNU56Q052ZmIvOQovRzNhYVhORmJuTzhtdjBoenpvbGVNV2dPRExuSis0Y1VBejNIM3RnY0N1OWJ6citaaHYwenZRbDlhOFlDbzZGClZ1V1BkVzByYmcxUE84dE91TXFBVG5ubzc5WkMvOUgzelM5bDdCdVkxVjJTbE5leXFUM1Z5T0ZGYzZTUkVwcHMKVEp1bDhRS0JnQXhuUUI4TUE3elBVTHUxY2x5YUpMZHRFZFJQa0tXTjdsS1lwdGMwZS9WSGZTc0t4c2VXa2ZxaQp6Z1haNTFrUVRyVDZaYjZIWVJmd0MxbU1YSFdSS1J5WWpBbkN4VmltNllRZCtLVlQ0OWlSRERBaUlGb01HQTRpCnZ2Y0lsbmVxT1paUERJb0tKNjBJak8vRFpIV2t3NW1MamFJclQrcVEzWEFHZEpBMTNoY20KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K` - -const authkeyInvalidP8 = `TUlHSEFnRUFNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQkcwd2F3SUJBUVFnRWJWemZQblpQeGZBeXhxRQpaVjA1bGFBb0pBbCsvNlh0Mk80bU9CNjExc09oUkFOQ0FBU2dGVEtqd0pBQVU5NWcrKy92ektXSGt6QVZtTk1JCnRCNXZUalpPT0l3bkViNzBNc1daRkl5VUZEMVA5R3dzdHo0K2FrSFg3dkk4Qkg2aEhtQm1mWlpaCg==` - -const authkeyValidP8 = `LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ0ViVnpmUG5aUHhmQXl4cUUKWlYwNWxhQW9KQWwrLzZYdDJPNG1PQjYxMXNPaFJBTkNBQVNnRlRLandKQUFVOTVnKysvdnpLV0hrekFWbU5NSQp0QjV2VGpaT09Jd25FYjcwTXNXWkZJeVVGRDFQOUd3c3R6NCtha0hYN3ZJOEJINmhIbUJtZmVRbAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==` +const ( + // nolint + certificateValidP12 = `MIIKlgIBAzCCClwGCSqGSIb3DQEHAaCCCk0EggpJMIIKRTCCBMcGCSqGSIb3DQEHBqCCBLgwggS0AgEAMIIErQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQID/GJtcRhjvwCAggAgIIEgE5ralQoQBDgHgdp5+EwBaMjcZEJUXmYRdVCttIwfN2OxlIs54tob3/wpUyWGqJ+UXy9X+4EsWpDPUfTN/w88GMgj0kftpTqG0+3Hu/9pkZO4pdLCiyMGOJnXCOdhHFirtTXAR3QvnKKIpXIKrmZ4rcr/24Uvd/u669Tz8VDgcGOQazKeyvtdW7TJBxMFRv+IsQi/qCj5PkQ0jBbZ1LAc4C8mCMwOcH+gi/e471mzPWihQmynH2yJlZ4jb+taxQ/b8Dhlni2vcIMn+HknRk3Cyo8jfFvvO0BjvVvEAPxPJt7X96VFFS2KlyXjY3zt0siGrzQpczgPB/1vTqhQUvoOBw6kcXWgOjwt+gR8Mmo2DELnQqGhbYuWu52doLgVvD+zGr5vLYXHz6gAXnI6FVyHb+oABeBet3cer3EzGR7r+VoLmWSBm8SyRHwi0mxE63S7oD1j22jaTo7jnQBFZaY+cPaATcFjqW67x4j8kXh9NRPoINSgodLJrgmet2D1iOKuLTkCWf0UTi2HUkn9Zf0y+IIViZaVE4mWaGb9xTBClfa4KwM5gSz3jybksFKbtnzzPFuzClu+2mdthJs/58Ao40eyaykNmzSPhDv1F8Mai8bfaAqSdcBl5ZB2PF33xhuNSS4j2uIh1ICGv9DueyN507iEMQO2yCcaQTMKejV7/52h9LReS5/QPXDJhWMVpTb5FGCP7EmO0lZTeBNO5MlDzDQfz5xcFqHqfoby2sfAMU8HNB8wzdcwHtacgKGLBjLkapxyTsqYE5Kry6UxclvF4soR8TZoQ69E7WsKZLmTaw2+msmnDJubpY0NqkRqkVk7umtVC0D+w6AIKDrY58HMlm80/ImgGXwybA1kuZMxqMzaH/xFiAHOSIGuVPtGgGFYNEdGbfOryuhFo9l1nSECWm8MN9hYwB1Rn9p6rkd+zrvbU1zv13drtrZ/vL0NlT02tlkS8NdWLGJkZhWgc2c89GyRb7mjuHRHu/BWGED3y7vjHo/lnkPsLJXw0ovIlqhtW0BtN/xSpGg0phDbn0Et5jb7Xmc+fWimgbtIUHcnJOV5QSYFzlR+kbzx0oKRARU4B3CWkdPeaXkrmw0IriS6vOdZcM8YBJ6BtXEDLsrSH7tHxeknYHLEl0uy9Oc1+Huyrz8j7Zxo8SQj9H+RX0HeMl8YB3HUBLHYcqCEBjm7mHI4rP8ULVkC5oCA5w3tJfMyvS/jZRiwMUyr0tiWhrh/AM3wPPX54cqozefojWKrqGtK9I+n0cfwW9rU3FsUcpMTo9uQ27O7NejKP2X/LLMZkQvWUEabZNjNrWsbp6d51/frfIR7kRlZAmmt2yS23h6w6RvKTAVUrNatEyzokfNAIDml6lYLweNJATZU08BznhPpuvh3bKOSos5uaJBYpsOYexoMGnAig428qypw0cmv6sCjO/xdIL86COVNQp/UtjcXJ9/E0bnVmzfpgA3WCy+29YXPx7DZ1U+bQ9jOO/P9pwqLwTH+gpcZiVm3ru1Tmiq6iZ8cG7tMLfTBNXljvtlDzCCBXYGCSqGSIb3DQEHAaCCBWcEggVjMIIFXzCCBVsGCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAgCvAo2HCM89AICCAAEggTIOcfaF6qWYXlo+BNBjYIllg0VwQSJXZmcqj2vXlDPIPrTuQ+QDmGnhYR6hVbcMrk3o7eQhH3ThyHM+KEzkYx1IAYCOdEQXYcFguoDG1CxHrgE1Y0H8yndc/yPw2tqkx6X9ZemdYp3welXZjYgUi9MKvGbN6lZ0cFTU+2+0+H/IyKQ3OUjDNymhOxypOPBaK2eQsJ7XumgJ6nLvNZDRx/f277J+LD/z0pOhzUOljhvA3dkBMpEvomX4erZihErunqP1jbH9O3eIYq9J7czGS2xuckolW19KqWOyWh8KRI/LnAqiEh2e0hZ7lpltj79PenO66VGPbn2f85A6b6PD4kipgoMB2IRibkoodyn/oo3WizO386fqtEfUlbFmxI4y4utobWe7nZ2VuBLgA/mgyyxqAJK1erM98NDWB/Njo1CPsaMl9ubXKPOyIZG0fOLUa23DfkJUEiCb839yKc2oEJkI0wtrvbeh1TAPv4vL4TxiXdiJ/6YrSa0/FQh6nqk1jiK+p22MzvEIkDOyPqk/GsAlc/k2kQ/M86tF50wtc08wnXv8+G8k6qTZ7VCluffzAUt64La47qj8XIfh7tKleznzQSbyjlNX8DsFVzGbCg9G4PKxrLAVnKEgIK1kOopSF1UUMqSKE0D3s5AURQhX8/Cf9h+WtNsWK+y7EMOntsBc2op0M7fQ9Jm73NF7CCYeqb0W7sziJSzqJsJgNp0+ArAcZQExeltxAb6kye3Z5JtP/oaB+jmcHKy9l/nhzKA3MzJwCZ5Q3oviPlNqJvFVBmGEEvC6iULLuv6VSxNdB2uH3Tsfa1TMOOHOadBTcyWatjscYS9ynkXuw1+8+FvEu3EV0UwopZmlSaYfMKQ2jshT4Cgg1zy15uKjomojtAaaF+D/U6KZVQk/7rzdaDmvkJvNtc5n9BW96tmrOhI6L+/WihS570qaitQUsHBBTOetlHXYEPiOkH8BhjzNHXLH9YpC8OEQOhO+1jEninDKNdbU7SCqV0+YE6kfR5Bfkw2MxoIQLtUnHjK6GR/q3fxo1TirbTe8c8dp907wgcXkT/rONX/iG1JTjxV2ixR1oM68LYI3eJzY801/xBSnmOjdzOPUHXCNHDTf9kPjkOtZWkGbZugf4ckRH/L8dK2Vo4QpFUN8AZjomanzLxjQZ+DVFNoPDT2K+0pezsMiwSJlyBGoIQHN0/2zVNVLo/KfARIOac1iC8+duj5S/1c52+PvP7FkMe72QUV0KUQ7AJHXUvQtFZx4Ny579/B/3c4D72CFSydhw3/+nL9+Nz956UafZ6G7HZ96frMTgajMcXQe1uXwgN2iTnnNtLdcC/ARHS1RkjgXHohO+VGuQxOo23PPABVaxex2SGGXX7Fc4MI2Xr4uaimZIzcUkuHUnhZQGkcFlVekZ/wJXookq0Fv8DuPuv7mGCx6BKERU9I+NMU6xLNe6VsfkS8t5uVq1EIINnddGl9VGpqOPN8EgU47gh6CcDkP8sxXsT8pZ1vQyJrUlWGYp68/okoQ+7lqnd06wzVDIwAE/+pq9PUxLdNvYE0sNe4JrEcKO0xp/zxCqLjHLT+rB896v2OsU0BA5tPQA7xkKp4PuQr6qO8fTVyfhImVmoFX6b9VgtLHIlJMVowIwYJKoZIhvcNAQkVMRYEFIwanwBmvSRCuV0e6/5ei8oEPXODMDMGCSqGSIb3DQEJFDEmHiQAQQBQAE4AUwAvADIAIABQAHIAaQB2AGEAdABlACAASwBlAHkwMTAhMAkGBSsOAwIaBQAEFK7XWCbKGSKmxNqE2E8dmCfwhaQxBAjPcbkv12ro6gICCAA=` + // nolint + certificateValidPEM = `QmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IDhDIDFBIDlGIDAwIDY2IEJEIDI0IDQyIEI5IDVEIDFFIEVCIEZFIDVFIDhCIENBIDA0IDNEIDczIDgzIAogICAgZnJpZW5kbHlOYW1lOiBBUE5TLzIgUHJpdmF0ZSBLZXkKc3ViamVjdD0vQz1OWi9TVD1XZWxsaW5ndG9uL0w9V2VsbGluZ3Rvbi9PPUludGVybmV0IFdpZGdpdHMgUHR5IEx0ZC9PVT05WkVINjJLUlZWL0NOPUFQTlMvMiBEZXZlbG9wbWVudCBJT1MgUHVzaCBTZXJ2aWNlczogY29tLnNpZGVzaG93LkFwbnMyCmlzc3Vlcj0vQz1OWi9TVD1XZWxsaW5ndG9uL0w9V2VsbGluZ3Rvbi9PPUFQTlMvMiBJbmMuL09VPUFQTlMvMiBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucy9DTj1BUE5TLzIgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ2ekNDQXRNQ0FRSXdEUVlKS29aSWh2Y05BUUVMQlFBd2djTXhDekFKQmdOVkJBWVRBazVhTVJNd0VRWUQKVlFRSUV3cFhaV3hzYVc1bmRHOXVNUk13RVFZRFZRUUhFd3BYWld4c2FXNW5kRzl1TVJRd0VnWURWUVFLRXd0QgpVRTVUTHpJZ1NXNWpMakV0TUNzR0ExVUVDeE1rUVZCT1V5OHlJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnClVtVnNZWFJwYjI1ek1VVXdRd1lEVlFRREV6eEJVRTVUTHpJZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlMKWld4aGRHbHZibk1nUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3dIaGNOTVRZd01UQTRNRGd6TkRNdwpXaGNOTWpZd01UQTFNRGd6TkRNd1dqQ0JzakVMTUFrR0ExVUVCaE1DVGxveEV6QVJCZ05WQkFnVENsZGxiR3hwCmJtZDBiMjR4RXpBUkJnTlZCQWNUQ2xkbGJHeHBibWQwYjI0eElUQWZCZ05WQkFvVEdFbHVkR1Z5Ym1WMElGZHAKWkdkcGRITWdVSFI1SUV4MFpERVRNQkVHQTFVRUN4TUtPVnBGU0RZeVMxSldWakZCTUQ4R0ExVUVBeE00UVZCTwpVeTh5SUVSbGRtVnNiM0J0Wlc1MElFbFBVeUJRZFhOb0lGTmxjblpwWTJWek9pQmpiMjB1YzJsa1pYTm9iM2N1ClFYQnVjekl3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRFkwYzFUS0I1b1pQd1EKN3QxQ3dNSXJ2cUI2R0lVM3RQeTZSaGNrWlhUa09COFllQldKN1VLZkN6OEhHSEZWb21CUDBUNU9VYmVxUXpxVwpZSmJRelo4YTZaTXN6YkwwbE80WDkrKzNPaTUvVHRBd09VT0s4ck9GTjI1bTJLZnNheUhRWi80dldTdEsyRndtCjVhSmJHTGxwSC9iLzd6MUQ0dmhtTWdvQnVUMUl1eWhHaXlGeGxaOUV0VGxvRnZzcU0xRTVmWVpPU1pBQ3lYVGEKSzR2ZGdiUU1nVVZzSTcxNEZBZ0xUbEswVWVpUmttS20zcGRidGZWYnJ0aHpJK0lIWEtJdFVJeStGbjIwUFJNaApkU25henRTejd0Z0JXQ0l4MjJxdmNZb2dIV2lPZ1VZSU03NzJ6RTJ5OFVWT3I4RHNpUmxzT0hTQTdFSTRNSmNRCkcyRlVxMlovQWdNQkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBR3lmeU8ySE1nY2RlQmN6M2J0NUJJTFgKZjdSQTIvVW1WSXdjS1IxcW90VHNGK1BuQm1jSUxleU9RZ0RlOXRHVTVjUmM3OWtEdDNKUm1NWVJPRklNZ0ZSZgpXZjIydU9LdGhvN0dRUWFLdkcrYmtnTVZkWUZSbEJIbkYrS2VxS0g4MXFiOXArQ1Q0SXcwR2VoSUwxRGlqRkxSClZJQUlCWXB6NG9CUENJRTFJU1ZUK0ZnYWYzSkFoNTlrYlBiTnc5QUlEeGFCdFA4RXV6U1ROd2ZieG9HYkNvYlMKV2kxVThJc0N3UUZ0OHRNMW00WlhEMUNjWklyR2RyeWVBaFZrdktJSlJpVTVRWVdJMm5xWk4rSnFRdWNtOWFkMAptWU81bUprSW9iVWE0K1pKaENQS0VkbWdwRmJSR2swd1Z1YURNOUN2NlAyc3JzWUFqYU80eTNWUDBHdk5LUkk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KQmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IDhDIDFBIDlGIDAwIDY2IEJEIDI0IDQyIEI5IDVEIDFFIEVCIEZFIDVFIDhCIENBIDA0IDNEIDczIDgzIAogICAgZnJpZW5kbHlOYW1lOiBBUE5TLzIgUHJpdmF0ZSBLZXkKS2V5IEF0dHJpYnV0ZXM6IDxObyBBdHRyaWJ1dGVzPgotLS0tLUJFR0lOIFJTQSBQUklWQVRFIEtFWS0tLS0tCk1JSUVvd0lCQUFLQ0FRRUEyTkhOVXlnZWFHVDhFTzdkUXNEQ0s3NmdlaGlGTjdUOHVrWVhKR1YwNURnZkdIZ1YKaWUxQ253cy9CeGh4VmFKZ1Q5RStUbEczcWtNNmxtQ1cwTTJmR3VtVExNMnk5SlR1Ri9mdnR6b3VmMDdRTURsRAppdkt6aFRkdVp0aW43R3NoMEdmK0wxa3JTdGhjSnVXaVd4aTVhUi8yLys4OVErTDRaaklLQWJrOVNMc29Sb3NoCmNaV2ZSTFU1YUJiN0tqTlJPWDJHVGttUUFzbDAyaXVMM1lHMERJRkZiQ085ZUJRSUMwNVN0Rkhva1pKaXB0NlgKVzdYMVc2N1ljeVBpQjF5aUxWQ012aFo5dEQwVElYVXAyczdVcys3WUFWZ2lNZHRxcjNHS0lCMW9qb0ZHQ0RPKwo5c3hOc3ZGRlRxL0E3SWtaYkRoMGdPeENPRENYRUJ0aFZLdG1md0lEQVFBQkFvSUJBUUNXOFpDSStPQWFlMXRFCmlwWjlGMmJXUDNMSExYVG84RllWZENBK1ZXZUlUazNQb2lJVWtKbVYwYVdDVWhEc3RndG81ZG9EZWo1c0NUdXIKWHZqL3luYWVyTWVxSkZZV2tld2p3WmNnTHlBWnZ3dU8xdjdmcDlFMHgvOVRHRGZuampuUE5lYXVuZHhXMGNOdAp6T1kzbDBIVkhzeTlKcGUzUURjQUpvdnk0VHY1K2hGWTRrRHhVQkdzeWp2aFNjVmdLZzV0TGtKY2xtM3NPdS9MCkd5THFwd05JM09KQWRNSXVWRDROMkJaMWFPRWFwNm1wMnk4SWUwL1I0WVdjYVo1QTRQdzd4VVBsNlNYYzl1dWEKLzc4UVRFUnRQQzZlanlDQmlFMDVhOG0zUTNpdWQzWHRubHl3czJLd2hnQkFmRTZNNHpSL2YzT1FCN1pJWE1oeQpacG1aWnc1eEFvR0JBUFluODRJcmxJUWV0V1FmdlBkTTdLemdoNlVESEN1Z25sQ0RnaHdZcFJKR2k4aE1mdVpWCnhOSXJZQUp6TFlEUTAxbEZKUkpnV1hUY2JxejlOQnoxbmhnK2NOT3oxL0tZKzM4ZXVkZWU2RE5ZbXp0UDdqRFAKMmpuYVMrZHRqQzhoQVhPYm5GcUcrTmlsTURMTHU2YVJtckphSW1ialNyZnlMaUU2bXZKN3U4MW5Bb0dCQU9GOQpnOTN3WjBtTDFyazJzNVd3SEdUTlUvSGFPdG1XUzR6N2tBN2Y0UWFSdWIrTXdwcFptbURaUEhwaVpYN0JQY1p6CmlPUFFoK3huN0lxUkdvUVdCTHlrQlZ0OHpaRm9MWkpvQ1IzbjYzbGV4NUE0cC8wUHAxZ0ZaclIreFg4UFlWb3MKM3llZWlXeVBLc1hYTmMwczVRd0haY1g2V2I4RUhUaFRYR0NCZXRjcEFvR0FNZVFKQzlJUGFQUGNhZTJ3M0NMQQpPWTNNa0ZwZ0JFdXFxc0RzeHdzTHNmZVFiMGxwMHYrQlErTzhzdUpyVDVlRHJxMUFCVWgzK1NLUVlBbDEzWVMrCnhVVXFrdzM1YjljbjZpenRGOUhDV0YzV0lLQmpzNHI5UFFxTXBkeGpORTRwUUNoQytXb3YxNkVyY3JBdVdXVmIKaUZpU2JtNFUvOUZiSGlzRnFxMy9jM01DZ1lCK3Z6U3VQZ0Z3MzcrMG9FRFZ0UVpneXVHU29wNU56Q052ZmIvOQovRzNhYVhORmJuTzhtdjBoenpvbGVNV2dPRExuSis0Y1VBejNIM3RnY0N1OWJ6citaaHYwenZRbDlhOFlDbzZGClZ1V1BkVzByYmcxUE84dE91TXFBVG5ubzc5WkMvOUgzelM5bDdCdVkxVjJTbE5leXFUM1Z5T0ZGYzZTUkVwcHMKVEp1bDhRS0JnQXhuUUI4TUE3elBVTHUxY2x5YUpMZHRFZFJQa0tXTjdsS1lwdGMwZS9WSGZTc0t4c2VXa2ZxaQp6Z1haNTFrUVRyVDZaYjZIWVJmd0MxbU1YSFdSS1J5WWpBbkN4VmltNllRZCtLVlQ0OWlSRERBaUlGb01HQTRpCnZ2Y0lsbmVxT1paUERJb0tKNjBJak8vRFpIV2t3NW1MamFJclQrcVEzWEFHZEpBMTNoY20KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K` + // nolint + authkeyInvalidP8 = `TUlHSEFnRUFNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQkcwd2F3SUJBUVFnRWJWemZQblpQeGZBeXhxRQpaVjA1bGFBb0pBbCsvNlh0Mk80bU9CNjExc09oUkFOQ0FBU2dGVEtqd0pBQVU5NWcrKy92ektXSGt6QVZtTk1JCnRCNXZUalpPT0l3bkViNzBNc1daRkl5VUZEMVA5R3dzdHo0K2FrSFg3dkk4Qkg2aEhtQm1mWlpaCg==` + // nolint + authkeyValidP8 = `LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ0ViVnpmUG5aUHhmQXl4cUUKWlYwNWxhQW9KQWwrLzZYdDJPNG1PQjYxMXNPaFJBTkNBQVNnRlRLandKQUFVOTVnKysvdnpLV0hrekFWbU5NSQp0QjV2VGpaT09Jd25FYjcwTXNXWkZJeVVGRDFQOUd3c3R6NCtha0hYN3ZJOEJINmhIbUJtZmVRbAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==` +) func TestDisabledAndroidIosConf(t *testing.T) { - PushConf, _ = config.LoadConf("") - PushConf.Android.Enabled = false - PushConf.Huawei.Enabled = false + cfg, _ := config.LoadConf() + cfg.Android.Enabled = false + cfg.Huawei.Enabled = false - err := CheckPushConf() + err := CheckPushConf(cfg) assert.Error(t, err) - assert.Equal(t, "Please enable iOS, Android or Huawei config in yml config", err.Error()) + assert.Equal(t, "please enable iOS, Android or Huawei config in yml config", err.Error()) } func TestMissingIOSCertificate(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "" - PushConf.Ios.KeyBase64 = "" - err := CheckPushConf() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = "" + err := CheckPushConf(cfg) assert.Error(t, err) - assert.Equal(t, "Missing iOS certificate key", err.Error()) + assert.Equal(t, "missing iOS certificate key", err.Error()) - PushConf.Ios.KeyPath = "test.pem" - err = CheckPushConf() + cfg.Ios.KeyPath = "test.pem" + err = CheckPushConf(cfg) assert.Error(t, err) assert.Equal(t, "certificate file does not exist", err.Error()) @@ -60,8 +61,8 @@ func TestIOSNotificationStructure(t *testing.T) { test := "test" expectBadge := 0 message := "Welcome notification Server" - expiration := int64(time.Now().Unix()) - req := PushNotification{ + expiration := time.Now().Unix() + req := &PushNotification{ ApnsID: test, Topic: test, Expiration: &expiration, @@ -82,7 +83,7 @@ func TestIOSNotificationStructure(t *testing.T) { URLArgs: []string{"a", "b"}, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -98,8 +99,8 @@ func TestIOSNotificationStructure(t *testing.T) { soundVolume, _ := jsonparser.GetFloat(data, "aps", "sound", "volume") contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available") category, _ := jsonparser.GetString(data, "aps", "category") - key1 := dat["key1"].(interface{}) - key2 := dat["key2"].(interface{}) + key1 := dat["key1"] + key2 := dat["key2"] aps := dat["aps"].(map[string]interface{}) urlArgs := aps["url-args"].([]interface{}) @@ -126,7 +127,7 @@ func TestIOSSoundAndVolume(t *testing.T) { test := "test" message := "Welcome notification Server" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -138,7 +139,7 @@ func TestIOSSoundAndVolume(t *testing.T) { }, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -162,7 +163,7 @@ func TestIOSSoundAndVolume(t *testing.T) { req.SoundName = "foobar" req.SoundVolume = 5.5 - notification = GetIOSNotification(req) + notification, _ = GetIOSNotification(req) dump, _ = json.Marshal(notification.Payload) data = []byte(string(dump)) @@ -177,7 +178,7 @@ func TestIOSSoundAndVolume(t *testing.T) { assert.Equal(t, int64(1), soundCritical) assert.Equal(t, "foobar", soundName) - req = PushNotification{ + req = &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -189,7 +190,7 @@ func TestIOSSoundAndVolume(t *testing.T) { }, } - notification = GetIOSNotification(req) + notification, _ = GetIOSNotification(req) dump, _ = json.Marshal(notification.Payload) data = []byte(string(dump)) @@ -204,7 +205,7 @@ func TestIOSSoundAndVolume(t *testing.T) { assert.Equal(t, int64(3), soundCritical) assert.Equal(t, "test", soundName) - req = PushNotification{ + req = &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -212,7 +213,7 @@ func TestIOSSoundAndVolume(t *testing.T) { Sound: "default", } - notification = GetIOSNotification(req) + notification, _ = GetIOSNotification(req) dump, _ = json.Marshal(notification.Payload) data = []byte(string(dump)) @@ -229,7 +230,7 @@ func TestIOSSummaryArg(t *testing.T) { test := "test" message := "Welcome notification Server" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -240,7 +241,7 @@ func TestIOSSummaryArg(t *testing.T) { }, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -263,7 +264,7 @@ func TestSendZeroValueForBadgeKey(t *testing.T) { test := "test" message := "Welcome notification Server" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -274,7 +275,7 @@ func TestSendZeroValueForBadgeKey(t *testing.T) { ThreadID: test, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -309,7 +310,7 @@ func TestSendZeroValueForBadgeKey(t *testing.T) { expectBadge := 10 req.Badge = &expectBadge - notification = GetIOSNotification(req) + notification, _ = GetIOSNotification(req) dump, _ = json.Marshal(notification.Payload) data = []byte(string(dump)) @@ -336,7 +337,7 @@ func TestCheckSilentNotification(t *testing.T) { var dat map[string]interface{} test := "test" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, CollapseID: test, @@ -344,7 +345,7 @@ func TestCheckSilentNotification(t *testing.T) { ContentAvailable: true, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -383,7 +384,7 @@ func TestAlertStringExample2ForIos(t *testing.T) { title := "Game Request" body := "Bob wants to play poker" actionLocKey := "PLAY" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -394,7 +395,7 @@ func TestAlertStringExample2ForIos(t *testing.T) { }, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -426,7 +427,7 @@ func TestAlertStringExample3ForIos(t *testing.T) { test := "test" badge := 9 sound := "bingbong.aiff" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -436,7 +437,7 @@ func TestAlertStringExample3ForIos(t *testing.T) { Sound: sound, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -457,7 +458,7 @@ func TestMessageAndTitle(t *testing.T) { test := "test" message := "Welcome notification Server" title := "Welcome notification Server title" - req := PushNotification{ + req := &PushNotification{ ApnsID: test, Topic: test, Priority: "normal", @@ -466,7 +467,7 @@ func TestMessageAndTitle(t *testing.T) { ContentAvailable: true, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -490,7 +491,7 @@ func TestMessageAndTitle(t *testing.T) { messageOverride := "Welcome notification Server overridden" req.Alert.Body = messageOverride - notification = GetIOSNotification(req) + notification, _ = GetIOSNotification(req) dump, _ = json.Marshal(notification.Payload) data = []byte(string(dump)) @@ -511,7 +512,7 @@ func TestIOSAlertNotificationStructure(t *testing.T) { var dat map[string]interface{} test := "test" - req := PushNotification{ + req := &PushNotification{ Message: "Welcome", Title: test, Alert: Alert{ @@ -527,7 +528,7 @@ func TestIOSAlertNotificationStructure(t *testing.T) { }, } - notification := GetIOSNotification(req) + notification, _ := GetIOSNotification(req) dump, _ := json.Marshal(notification.Payload) data := []byte(string(dump)) @@ -564,166 +565,130 @@ func TestIOSAlertNotificationStructure(t *testing.T) { assert.Contains(t, locArgs, "b") } -func TestDisabledIosNotifications(t *testing.T) { - ctx := context.Background() - PushConf, _ = config.LoadConf("") - - PushConf.Ios.Enabled = false - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() - assert.Nil(t, err) - - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") - - androidToken := os.Getenv("ANDROID_TEST_TOKEN") - - req := RequestPush{ - Notifications: []PushNotification{ - // ios - { - Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, - Platform: PlatFormIos, - Message: "Welcome", - }, - // android - { - Tokens: []string{androidToken, androidToken + "_"}, - Platform: PlatFormAndroid, - Message: "Welcome", - }, - }, - } - - count, logs := queueNotification(ctx, req) - assert.Equal(t, 2, count) - assert.Equal(t, 0, len(logs)) -} - func TestWrongIosCertificateExt(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "test" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "test" + err := InitAPNSClient(cfg) assert.Error(t, err) assert.Equal(t, "wrong certificate key extension", err.Error()) - PushConf.Ios.KeyPath = "" - PushConf.Ios.KeyBase64 = "abcd" - PushConf.Ios.KeyType = "abcd" - err = InitAPNSClient() + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = "abcd" + cfg.Ios.KeyType = "abcd" + err = InitAPNSClient(cfg) assert.Error(t, err) assert.Equal(t, "wrong certificate key type", err.Error()) } func TestAPNSClientDevHost(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.p12" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.p12" + err := InitAPNSClient(cfg) assert.Nil(t, err) assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) - PushConf.Ios.KeyPath = "" - PushConf.Ios.KeyBase64 = certificateValidP12 - PushConf.Ios.KeyType = "p12" - err = InitAPNSClient() + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = certificateValidP12 + cfg.Ios.KeyType = "p12" + err = InitAPNSClient(cfg) assert.Nil(t, err) assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) } func TestAPNSClientProdHost(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.Production = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.Production = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := InitAPNSClient(cfg) assert.Nil(t, err) assert.Equal(t, apns2.HostProduction, ApnsClient.Host) - PushConf.Ios.KeyPath = "" - PushConf.Ios.KeyBase64 = certificateValidPEM - PushConf.Ios.KeyType = "pem" - err = InitAPNSClient() + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = certificateValidPEM + cfg.Ios.KeyType = "pem" + err = InitAPNSClient(cfg) assert.Nil(t, err) assert.Equal(t, apns2.HostProduction, ApnsClient.Host) } func TestAPNSClientInvaildToken(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/authkey-invalid.p8" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/authkey-invalid.p8" + err := InitAPNSClient(cfg) assert.Error(t, err) - PushConf.Ios.KeyPath = "" - PushConf.Ios.KeyBase64 = authkeyInvalidP8 - PushConf.Ios.KeyType = "p8" - err = InitAPNSClient() + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = authkeyInvalidP8 + cfg.Ios.KeyType = "p8" + err = InitAPNSClient(cfg) assert.Error(t, err) // empty key-id or team-id - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/authkey-valid.p8" - err = InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/authkey-valid.p8" + err = InitAPNSClient(cfg) assert.Error(t, err) - PushConf.Ios.KeyID = "key-id" - PushConf.Ios.TeamID = "" - err = InitAPNSClient() + cfg.Ios.KeyID = "key-id" + cfg.Ios.TeamID = "" + err = InitAPNSClient(cfg) assert.Error(t, err) - PushConf.Ios.KeyID = "" - PushConf.Ios.TeamID = "team-id" - err = InitAPNSClient() + cfg.Ios.KeyID = "" + cfg.Ios.TeamID = "team-id" + err = InitAPNSClient(cfg) assert.Error(t, err) } func TestAPNSClientVaildToken(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/authkey-valid.p8" - PushConf.Ios.KeyID = "key-id" - PushConf.Ios.TeamID = "team-id" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/authkey-valid.p8" + cfg.Ios.KeyID = "key-id" + cfg.Ios.TeamID = "team-id" + err := InitAPNSClient(cfg) assert.NoError(t, err) assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) - PushConf.Ios.Production = true - err = InitAPNSClient() + cfg.Ios.Production = true + err = InitAPNSClient(cfg) assert.NoError(t, err) assert.Equal(t, apns2.HostProduction, ApnsClient.Host) // test base64 - PushConf.Ios.Production = false - PushConf.Ios.KeyPath = "" - PushConf.Ios.KeyBase64 = authkeyValidP8 - PushConf.Ios.KeyType = "p8" - err = InitAPNSClient() + cfg.Ios.Production = false + cfg.Ios.KeyPath = "" + cfg.Ios.KeyBase64 = authkeyValidP8 + cfg.Ios.KeyType = "p8" + err = InitAPNSClient(cfg) assert.NoError(t, err) assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) - PushConf.Ios.Production = true - err = InitAPNSClient() + cfg.Ios.Production = true + err = InitAPNSClient(cfg) assert.NoError(t, err) assert.Equal(t, apns2.HostProduction, ApnsClient.Host) } func TestAPNSClientUseProxy(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.p12" - PushConf.Core.HTTPProxy = "http://127.0.0.1:8080" - _ = SetProxy(PushConf.Core.HTTPProxy) - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.p12" + cfg.Core.HTTPProxy = "http://127.0.0.1:8080" + _ = SetProxy(cfg.Core.HTTPProxy) + err := InitAPNSClient(cfg) assert.Nil(t, err) assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) @@ -731,13 +696,13 @@ func TestAPNSClientUseProxy(t *testing.T) { actualProxyURL, err := ApnsClient.HTTPClient.Transport.(*http.Transport).Proxy(req) assert.Nil(t, err) - expectedProxyURL, _ := url.ParseRequestURI(PushConf.Core.HTTPProxy) + expectedProxyURL, _ := url.ParseRequestURI(cfg.Core.HTTPProxy) assert.Equal(t, expectedProxyURL, actualProxyURL) - PushConf.Ios.KeyPath = "../certificate/authkey-valid.p8" - PushConf.Ios.TeamID = "example.team" - PushConf.Ios.KeyID = "example.key" - err = InitAPNSClient() + cfg.Ios.KeyPath = "../certificate/authkey-valid.p8" + cfg.Ios.TeamID = "example.team" + cfg.Ios.KeyID = "example.key" + err = InitAPNSClient(cfg) assert.Nil(t, err) assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) assert.NotNil(t, ApnsClient.Token) @@ -746,61 +711,64 @@ func TestAPNSClientUseProxy(t *testing.T) { actualProxyURL, err = ApnsClient.HTTPClient.Transport.(*http.Transport).Proxy(req) assert.Nil(t, err) - expectedProxyURL, _ = url.ParseRequestURI(PushConf.Core.HTTPProxy) + expectedProxyURL, _ = url.ParseRequestURI(cfg.Core.HTTPProxy) assert.Equal(t, expectedProxyURL, actualProxyURL) http.DefaultTransport.(*http.Transport).Proxy = nil } func TestPushToIOS(t *testing.T) { - PushConf, _ = config.LoadConf("") - MaxConcurrentIOSPushes = make(chan struct{}, PushConf.Ios.MaxConcurrentPushes) + cfg, _ := config.LoadConf() + MaxConcurrentIOSPushes = make(chan struct{}, cfg.Ios.MaxConcurrentPushes) - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := InitAPNSClient(cfg) assert.Nil(t, err) - err = InitAppStatus() + err = status.InitAppStatus(cfg) assert.Nil(t, err) - req := PushNotification{ + req := &PushNotification{ + // nolint Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7", "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef1"}, Platform: 1, Message: "Welcome", } // send fail - PushToIOS(req) + resp, err := PushToIOS(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 2) } func TestApnsHostFromRequest(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Ios.Enabled = true - PushConf.Ios.KeyPath = "../certificate/certificate-valid.pem" - err := InitAPNSClient() + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := InitAPNSClient(cfg) assert.Nil(t, err) - err = InitAppStatus() + err = status.InitAppStatus(cfg) assert.Nil(t, err) - req := PushNotification{ + req := &PushNotification{ Production: true, } - client := getApnsClient(req) + client := getApnsClient(cfg, req) assert.Equal(t, apns2.HostProduction, client.Host) - req = PushNotification{ + req = &PushNotification{ Development: true, } - client = getApnsClient(req) + client = getApnsClient(cfg, req) assert.Equal(t, apns2.HostDevelopment, client.Host) - req = PushNotification{} - PushConf.Ios.Production = true - client = getApnsClient(req) + req = &PushNotification{} + cfg.Ios.Production = true + client = getApnsClient(cfg, req) assert.Equal(t, apns2.HostProduction, client.Host) - PushConf.Ios.Production = false - client = getApnsClient(req) + cfg.Ios.Production = false + client = getApnsClient(cfg, req) assert.Equal(t, apns2.HostDevelopment, client.Host) } diff --git a/gorush/notification_fcm.go b/notify/notification_fcm.go similarity index 53% rename from gorush/notification_fcm.go rename to notify/notification_fcm.go index de1129c46..2c906d9a7 100644 --- a/gorush/notification_fcm.go +++ b/notify/notification_fcm.go @@ -1,27 +1,32 @@ -package gorush +package notify import ( + "encoding/json" "errors" "fmt" + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + "github.com/appleboy/go-fcm" - "github.com/sirupsen/logrus" ) // InitFCMClient use for initialize FCM Client. -func InitFCMClient(key string) (*fcm.Client, error) { +func InitFCMClient(cfg *config.ConfYaml, key string) (*fcm.Client, error) { var err error - if key == "" { + if key == "" && cfg.Android.APIKey == "" { return nil, errors.New("Missing Android API Key") } - if key != PushConf.Android.APIKey { + if key != "" && key != cfg.Android.APIKey { return fcm.NewClient(key) } if FCMClient == nil { - FCMClient, err = fcm.NewClient(key) + FCMClient, err = fcm.NewClient(cfg.Android.APIKey) return FCMClient, err } @@ -31,7 +36,7 @@ func InitFCMClient(key string) (*fcm.Client, error) { // GetAndroidNotification use for define Android notification. // HTTP Connection Server Reference for Android // https://firebase.google.com/docs/cloud-messaging/http-server-ref -func GetAndroidNotification(req PushNotification) *fcm.Message { +func GetAndroidNotification(req PushNotification) (*fcm.Message, error) { notification := &fcm.Message{ To: req.To, Condition: req.Condition, @@ -97,17 +102,25 @@ func GetAndroidNotification(req PushNotification) *fcm.Message { notification.Apns = req.Apns } - return notification + jsonMarshall, err := json.Marshal(notification) + if err != nil { + LogError.Error("Failed to marshal the default message! Error is " + err.Error()) + return nil, err + } + + LogAccess.Debugf("Default message is %s", string(jsonMarshall)) + + return notification, nil } // PushToAndroid provide send notification to Android server. -func PushToAndroid(req PushNotification) { - LogAccess.Debug("Start push notification for Android") +func PushToAndroid(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for Android") var ( client *fcm.Client retryCount = 0 - maxRetry = PushConf.Android.MaxRetry + maxRetry = cfg.Android.MaxRetry ) if req.Retry > 0 && req.Retry < maxRetry { @@ -115,68 +128,54 @@ func PushToAndroid(req PushNotification) { } // check message - err := CheckMessage(req) + err = CheckMessage(req) if err != nil { - LogError.Error("request error: " + err.Error()) + logx.LogError.Error("request error: " + err.Error()) return } + resp = &ResponsePush{} + Retry: - notification := GetAndroidNotification(req) + notification, _ := GetAndroidNotification(req) if req.APIKey != "" { - client, err = InitFCMClient(req.APIKey) + client, err = InitFCMClient(cfg, req.APIKey) } else { - client, err = InitFCMClient(PushConf.Android.APIKey) + client, err = InitFCMClient(cfg, cfg.Android.APIKey) } if err != nil { // FCM server error - LogError.Error("FCM server error: " + err.Error()) + logx.LogError.Error("FCM server error: " + err.Error()) return } res, err := client.Send(notification) if err != nil { // Send Message error - LogError.Error("FCM server send message error: " + err.Error()) + logx.LogError.Error("FCM server send message error: " + err.Error()) if req.IsTopic() { - if PushConf.Core.Sync { - req.AddLog(getLogPushEntry(FailedPush, req.To, req, err)) - } else if PushConf.Core.FeedbackURL != "" { - go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) { - err := DispatchFeedback(log, url, timeout) - if err != nil { - logger.Error(err) - } - }(LogError, getLogPushEntry(FailedPush, req.To, req, err), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout) - } - StatStorage.AddAndroidError(1) + errLog := logPush(cfg, core.FailedPush, req.To, req, err) + resp.Logs = append(resp.Logs, errLog) + status.StatStorage.AddAndroidError(1) } else { for _, token := range req.Tokens { - if PushConf.Core.Sync { - req.AddLog(getLogPushEntry(FailedPush, token, req, err)) - } else if PushConf.Core.FeedbackURL != "" { - go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) { - err := DispatchFeedback(log, url, timeout) - if err != nil { - logger.Error(err) - } - }(LogError, getLogPushEntry(FailedPush, token, req, err), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout) - } + errLog := logPush(cfg, core.FailedPush, token, req, err) + resp.Logs = append(resp.Logs, errLog) } - StatStorage.AddAndroidError(int64(len(req.Tokens))) + status.StatStorage.AddAndroidError(int64(len(req.Tokens))) } return } if !req.IsTopic() { - LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) + logx.LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) } - StatStorage.AddAndroidSuccess(int64(res.Success)) - StatStorage.AddAndroidError(int64(res.Failure)) + status.StatStorage.AddAndroidSuccess(int64(res.Success)) + status.StatStorage.AddAndroidError(int64(res.Failure)) var newTokens []string // result from Send messages to specific devices @@ -195,21 +194,12 @@ Retry: newTokens = append(newTokens, to) } - LogPush(FailedPush, to, req, result.Error) - if PushConf.Core.Sync { - req.AddLog(getLogPushEntry(FailedPush, to, req, result.Error)) - } else if PushConf.Core.FeedbackURL != "" { - go func(logger *logrus.Logger, log LogPushEntry, url string, timeout int64) { - err := DispatchFeedback(log, url, timeout) - if err != nil { - logger.Error(err) - } - }(LogError, getLogPushEntry(FailedPush, to, req, result.Error), PushConf.Core.FeedbackURL, PushConf.Core.FeedbackTimeout) - } + errLog := logPush(cfg, core.FailedPush, to, req, result.Error) + resp.Logs = append(resp.Logs, errLog) continue } - LogPush(SucceededPush, to, req, nil) + logPush(cfg, core.SucceededPush, to, req, nil) } // result from Send messages to topics @@ -220,16 +210,14 @@ Retry: } else { to = req.Condition } - LogAccess.Debug("Send Topic Message: ", to) + logx.LogAccess.Debug("Send Topic Message: ", to) // Success if res.MessageID != 0 { - LogPush(SucceededPush, to, req, nil) + logPush(cfg, core.SucceededPush, to, req, nil) } else { // failure - LogPush(FailedPush, to, req, res.Error) - if PushConf.Core.Sync { - req.AddLog(getLogPushEntry(FailedPush, to, req, res.Error)) - } + errLog := logPush(cfg, core.FailedPush, to, req, res.Error) + resp.Logs = append(resp.Logs, errLog) } } @@ -237,10 +225,9 @@ Retry: if len(res.FailedRegistrationIDs) > 0 { newTokens = append(newTokens, res.FailedRegistrationIDs...) - LogPush(FailedPush, notification.To, req, errors.New("device group: partial success or all fails")) - if PushConf.Core.Sync { - req.AddLog(getLogPushEntry(FailedPush, notification.To, req, errors.New("device group: partial success or all fails"))) - } + // nolint + errLog := logPush(cfg, core.FailedPush, notification.To, req, errors.New("device group: partial success or all fails")) + resp.Logs = append(resp.Logs, errLog) } if len(newTokens) > 0 && retryCount < maxRetry { @@ -250,4 +237,19 @@ Retry: req.Tokens = newTokens goto Retry } + + return resp, nil +} + +func logPush(cfg *config.ConfYaml, status, token string, req *PushNotification, err error) logx.LogPushEntry { + return logx.LogPush(&logx.InputLog{ + ID: req.ID, + Status: status, + Token: token, + Message: req.Message, + Platform: req.Platform, + Error: err, + HideToken: cfg.Log.HideToken, + Format: cfg.Log.Format, + }) } diff --git a/gorush/notification_fcm_test.go b/notify/notification_fcm_test.go similarity index 66% rename from gorush/notification_fcm_test.go rename to notify/notification_fcm_test.go index b9106e85f..8623bb051 100644 --- a/gorush/notification_fcm_test.go +++ b/notify/notification_fcm_test.go @@ -1,47 +1,32 @@ -package gorush +package notify import ( - "context" - "log" "os" - "sync" "testing" - "github.com/appleboy/go-fcm" "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + + "github.com/appleboy/go-fcm" "github.com/stretchr/testify/assert" ) -func init() { - PushConf, _ = config.LoadConf("") - if err := InitLog(); err != nil { - log.Fatal(err) - } - - ctx := context.Background() - wg := &sync.WaitGroup{} - wg.Add(int(PushConf.Core.WorkerNum)) - InitWorkers(ctx, wg, PushConf.Core.WorkerNum, PushConf.Core.QueueNum) - - if err := InitAppStatus(); err != nil { - log.Fatal(err) - } -} - func TestMissingAndroidAPIKey(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Android.Enabled = true - PushConf.Android.APIKey = "" + cfg.Android.Enabled = true + cfg.Android.APIKey = "" - err := CheckPushConf() + err := CheckPushConf(cfg) assert.Error(t, err) assert.Equal(t, "Missing Android API Key", err.Error()) } func TestMissingKeyForInitFCMClient(t *testing.T) { - client, err := InitFCMClient("") + cfg, _ := config.LoadConf() + cfg.Android.APIKey = "" + client, err := InitFCMClient(cfg, "") assert.Nil(t, client) assert.Error(t, err) @@ -49,88 +34,92 @@ func TestMissingKeyForInitFCMClient(t *testing.T) { } func TestPushToAndroidWrongToken(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") - req := PushNotification{ + req := &PushNotification{ Tokens: []string{"aaaaaa", "bbbbb"}, - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Message: "Welcome", } // Android Success count: 0, Failure count: 2 - PushToAndroid(req) + resp, err := PushToAndroid(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 2) } func TestPushToAndroidRightTokenForJSONLog(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") // log for json - PushConf.Log.Format = "json" + cfg.Log.Format = "json" androidToken := os.Getenv("ANDROID_TEST_TOKEN") - req := PushNotification{ + req := &PushNotification{ Tokens: []string{androidToken}, - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Message: "Welcome", } - PushToAndroid(req) + resp, err := PushToAndroid(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 0) } func TestPushToAndroidRightTokenForStringLog(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") androidToken := os.Getenv("ANDROID_TEST_TOKEN") - req := PushNotification{ + req := &PushNotification{ Tokens: []string{androidToken}, - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Message: "Welcome", } - PushToAndroid(req) + resp, err := PushToAndroid(req, cfg) + assert.Nil(t, err) + assert.Len(t, resp.Logs, 0) } func TestOverwriteAndroidAPIKey(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Core.Sync = true - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Core.Sync = true + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") androidToken := os.Getenv("ANDROID_TEST_TOKEN") - req := PushNotification{ + req := &PushNotification{ Tokens: []string{androidToken, "bbbbb"}, - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Message: "Welcome", // overwrite android api key APIKey: "1234", - - log: &[]LogPushEntry{}, } // FCM server error: 401 error: 401 Unauthorized (Wrong API Key) - PushToAndroid(req) + resp, err := PushToAndroid(req, cfg) - assert.Len(t, *req.log, 2) + assert.Error(t, err) + assert.Len(t, resp.Logs, 2) } func TestFCMMessage(t *testing.T) { - var req PushNotification var err error // the message must specify at least one registration ID - req = PushNotification{ + req := &PushNotification{ Message: "Test", Tokens: []string{}, } @@ -139,7 +128,7 @@ func TestFCMMessage(t *testing.T) { assert.Error(t, err) // the token must not be empty - req = PushNotification{ + req = &PushNotification{ Message: "Test", Tokens: []string{""}, } @@ -148,9 +137,9 @@ func TestFCMMessage(t *testing.T) { assert.Error(t, err) // ignore check token length if send topic message - req = PushNotification{ + req = &PushNotification{ Message: "Test", - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, To: "/topics/foo-bar", } @@ -158,9 +147,9 @@ func TestFCMMessage(t *testing.T) { assert.NoError(t, err) // "condition": "'dogs' in topics || 'cats' in topics", - req = PushNotification{ + req = &PushNotification{ Message: "Test", - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Condition: "'dogs' in topics || 'cats' in topics", } @@ -168,9 +157,9 @@ func TestFCMMessage(t *testing.T) { assert.NoError(t, err) // the message may specify at most 1000 registration IDs - req = PushNotification{ + req = &PushNotification{ Message: "Test", - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Tokens: make([]string, 1001), } @@ -180,9 +169,9 @@ func TestFCMMessage(t *testing.T) { // the message's TimeToLive field must be an integer // between 0 and 2419200 (4 weeks) timeToLive := uint(2419201) - req = PushNotification{ + req = &PushNotification{ Message: "Test", - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Tokens: []string{"XXXXXXXXX"}, TimeToLive: &timeToLive, } @@ -192,9 +181,9 @@ func TestFCMMessage(t *testing.T) { // Pass timeToLive = uint(86400) - req = PushNotification{ + req = &PushNotification{ Message: "Test", - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Tokens: []string{"XXXXXXXXX"}, TimeToLive: &timeToLive, } @@ -204,26 +193,29 @@ func TestFCMMessage(t *testing.T) { } func TestCheckAndroidMessage(t *testing.T) { - PushConf, _ = config.LoadConf("") + cfg, _ := config.LoadConf() - PushConf.Android.Enabled = true - PushConf.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") timeToLive := uint(2419201) - req := PushNotification{ + req := &PushNotification{ Tokens: []string{"aaaaaa", "bbbbb"}, - Platform: PlatFormAndroid, + Platform: core.PlatFormAndroid, Message: "Welcome", TimeToLive: &timeToLive, } - PushToAndroid(req) + // the message's TimeToLive field must be an integer between 0 and 2419200 (4 weeks) + resp, err := PushToAndroid(req, cfg) + assert.NotNil(t, err) + assert.Nil(t, resp) } func TestAndroidNotificationStructure(t *testing.T) { test := "test" timeToLive := uint(100) - req := PushNotification{ + req := &PushNotification{ Tokens: []string{"a", "b"}, Message: "Welcome", To: test, @@ -247,7 +239,7 @@ func TestAndroidNotificationStructure(t *testing.T) { }, } - notification := GetAndroidNotification(req) + notification, _ := GetAndroidNotification(req) assert.Equal(t, test, notification.To) assert.Equal(t, "high", notification.Priority) @@ -266,14 +258,14 @@ func TestAndroidNotificationStructure(t *testing.T) { assert.Equal(t, 2, notification.Data["b"]) // test empty body - req = PushNotification{ + req = &PushNotification{ Tokens: []string{"a", "b"}, To: test, Notification: &fcm.Notification{ Body: "", }, } - notification = GetAndroidNotification(req) + notification, _ = GetAndroidNotification(req) assert.Equal(t, test, notification.To) assert.Equal(t, "", notification.Notification.Body) diff --git a/gorush/notification_hms.go b/notify/notification_hms.go similarity index 67% rename from gorush/notification_hms.go rename to notify/notification_hms.go index 6d373fe36..12cf80afc 100644 --- a/gorush/notification_hms.go +++ b/notify/notification_hms.go @@ -1,26 +1,30 @@ -package gorush +package notify import ( "context" - "encoding/json" "errors" "sync" - "github.com/msalihkarakasli/go-hms-push/push/config" - "github.com/msalihkarakasli/go-hms-push/push/core" + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/status" + + c "github.com/msalihkarakasli/go-hms-push/push/config" + client "github.com/msalihkarakasli/go-hms-push/push/core" "github.com/msalihkarakasli/go-hms-push/push/model" ) var ( pushError error - pushClient *core.HMSClient + pushClient *client.HMSClient once sync.Once ) // GetPushClient use for create HMS Push -func GetPushClient(conf *config.Config) (*core.HMSClient, error) { +func GetPushClient(conf *c.Config) (*client.HMSClient, error) { once.Do(func() { - client, err := core.NewHttpClient(conf) + client, err := client.NewHttpClient(conf) if err != nil { panic(err) } @@ -32,7 +36,7 @@ func GetPushClient(conf *config.Config) (*core.HMSClient, error) { } // InitHMSClient use for initialize HMS Client. -func InitHMSClient(appSecret, appID string) (*core.HMSClient, error) { +func InitHMSClient(cfg *config.ConfYaml, appSecret, appID string) (*client.HMSClient, error) { if appSecret == "" { return nil, errors.New("Missing Huawei App Secret") } @@ -41,14 +45,14 @@ func InitHMSClient(appSecret, appID string) (*core.HMSClient, error) { return nil, errors.New("Missing Huawei App ID") } - conf := &config.Config{ + conf := &c.Config{ AppId: appID, AppSecret: appSecret, AuthUrl: "https://oauth-login.cloud.huawei.com/oauth2/v3/token", PushUrl: "https://push-api.cloud.huawei.com", } - if appSecret != PushConf.Huawei.AppSecret || appID != PushConf.Huawei.AppID { + if appSecret != cfg.Huawei.AppSecret || appID != cfg.Huawei.AppID { return GetPushClient(conf) } @@ -62,7 +66,7 @@ func InitHMSClient(appSecret, appID string) (*core.HMSClient, error) { // GetHuaweiNotification use for define HMS notification. // HTTP Connection Server Reference for HMS // https://developer.huawei.com/consumer/en/doc/development/HMS-References/push-sendapi -func GetHuaweiNotification(req PushNotification) (*model.MessageRequest, error) { +func GetHuaweiNotification(req *PushNotification) (*model.MessageRequest, error) { msgRequest := model.NewNotificationMsgRequest() msgRequest.Message.Android = model.GetDefaultAndroid() @@ -153,22 +157,22 @@ func GetHuaweiNotification(req PushNotification) (*model.MessageRequest, error) b, err := json.Marshal(msgRequest) if err != nil { - LogError.Error("Failed to marshal the default message! Error is " + err.Error()) + logx.LogError.Error("Failed to marshal the default message! Error is " + err.Error()) return nil, err } - LogAccess.Debugf("Default message is %s", string(b)) + logx.LogAccess.Debugf("Default message is %s", string(b)) return msgRequest, nil } // PushToHuawei provide send notification to Android server. -func PushToHuawei(req PushNotification) bool { - LogAccess.Debug("Start push notification for Huawei") +func PushToHuawei(req *PushNotification, cfg *config.ConfYaml) (resp *ResponsePush, err error) { + logx.LogAccess.Debug("Start push notification for Huawei") var ( - client *core.HMSClient + client *client.HMSClient retryCount = 0 - maxRetry = PushConf.Huawei.MaxRetry + maxRetry = cfg.Huawei.MaxRetry ) if req.Retry > 0 && req.Retry < maxRetry { @@ -176,40 +180,44 @@ func PushToHuawei(req PushNotification) bool { } // check message - err := CheckMessage(req) + err = CheckMessage(req) if err != nil { - LogError.Error("request error: " + err.Error()) - return false + logx.LogError.Error("request error: " + err.Error()) + return } -Retry: - isError := false - - notification, _ := GetHuaweiNotification(req) - - client, err = InitHMSClient(PushConf.Huawei.AppSecret, PushConf.Huawei.AppID) + client, err = InitHMSClient(cfg, cfg.Huawei.AppSecret, cfg.Huawei.AppID) if err != nil { // HMS server error - LogError.Error("HMS server error: " + err.Error()) - return false + logx.LogError.Error("HMS server error: " + err.Error()) + return } + resp = &ResponsePush{} + +Retry: + isError := false + + notification, _ := GetHuaweiNotification(req) + res, err := client.SendMessage(context.Background(), notification) if err != nil { // Send Message error - LogError.Error("HMS server send message error: " + err.Error()) - return false + errLog := logPush(cfg, core.FailedPush, req.To, req, err) + resp.Logs = append(resp.Logs, errLog) + logx.LogError.Error("HMS server send message error: " + err.Error()) + return } // Huawei Push Send API does not support exact results for each token if res.Code == "80000000" { - StatStorage.AddHuaweiSuccess(int64(1)) - LogAccess.Debug("Huwaei Send Notification is completed successfully!") + status.StatStorage.AddHuaweiSuccess(int64(1)) + logx.LogAccess.Debug("Huwaei Send Notification is completed successfully!") } else { isError = true - StatStorage.AddHuaweiError(int64(1)) - LogAccess.Debug("Huawei Send Notification is failed! Code: " + res.Code) + status.StatStorage.AddHuaweiError(int64(1)) + logx.LogAccess.Debug("Huawei Send Notification is failed! Code: " + res.Code) } if isError && retryCount < maxRetry { @@ -219,5 +227,5 @@ Retry: goto Retry } - return isError + return resp, nil } diff --git a/notify/notification_hms_test.go b/notify/notification_hms_test.go new file mode 100644 index 000000000..27e1d8490 --- /dev/null +++ b/notify/notification_hms_test.go @@ -0,0 +1,50 @@ +package notify + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestMissingHuaweiAppSecret(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Huawei.Enabled = true + cfg.Huawei.AppSecret = "" + + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App Secret", err.Error()) +} + +func TestMissingHuaweiAppID(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Huawei.Enabled = true + cfg.Huawei.AppID = "" + + err := CheckPushConf(cfg) + + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App ID", err.Error()) +} + +func TestMissingAppSecretForInitHMSClient(t *testing.T) { + cfg, _ := config.LoadConf() + client, err := InitHMSClient(cfg, "", "APP_SECRET") + + assert.Nil(t, client) + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App Secret", err.Error()) +} + +func TestMissingAppIDForInitHMSClient(t *testing.T) { + cfg, _ := config.LoadConf() + client, err := InitHMSClient(cfg, "APP_ID", "") + + assert.Nil(t, client) + assert.Error(t, err) + assert.Equal(t, "Missing Huawei App ID", err.Error()) +} diff --git a/notify/notification_test.go b/notify/notification_test.go new file mode 100644 index 000000000..559a5bb95 --- /dev/null +++ b/notify/notification_test.go @@ -0,0 +1,34 @@ +package notify + +import ( + "testing" + + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" +) + +func TestCorrectConf(t *testing.T) { + cfg, _ := config.LoadConf() + + cfg.Android.Enabled = true + cfg.Android.APIKey = "xxxxx" + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + + err := CheckPushConf(cfg) + + assert.NoError(t, err) +} + +func TestSetProxyURL(t *testing.T) { + err := SetProxy("87.236.233.92:8080") + assert.Error(t, err) + assert.Equal(t, "parse \"87.236.233.92:8080\": invalid URI for request", err.Error()) + + err = SetProxy("a.html") + assert.Error(t, err) + + err = SetProxy("http://87.236.233.92:8080") + assert.NoError(t, err) +} diff --git a/pipeline.libsonnet b/pipeline.libsonnet index 2fc599750..0f8b5de7e 100644 --- a/pipeline.libsonnet +++ b/pipeline.libsonnet @@ -7,40 +7,12 @@ arch: 'amd64', }, steps: [ - { - name: 'vet', - image: 'golang:1.16', - pull: 'always', - commands: [ - 'make vet', - ], - volumes: [ - { - name: 'gopath', - path: '/go', - }, - ], - }, { name: 'lint', - image: 'golang:1.16', - pull: 'always', - commands: [ - 'make lint', - ], - volumes: [ - { - name: 'gopath', - path: '/go', - }, - ], - }, - { - name: 'misspell', - image: 'golang:1.16', + image: 'golangci/golangci-lint:v1.41.1', pull: 'always', commands: [ - 'make misspell-check', + 'golangci-lint run -v', ], volumes: [ { @@ -118,6 +90,13 @@ name: 'redis', image: 'redis', }, + { + name: 'nsq', + image: 'nsqio/nsq', + commands: [ + "/nsqd", + ], + }, ], }, @@ -183,22 +162,6 @@ './release/' + os + '/' + arch + '/' + name + ' --help', ], }, - { - name: 'dryrun', - image: 'plugins/docker:' + os + '-' + arch, - pull: 'always', - settings: { - daemon_off: false, - dry_run: true, - tags: os + '-' + arch, - dockerfile: 'docker/Dockerfile.' + os + '.' + arch, - repo: 'appleboy/' + name, - cache_from: 'appleboy/' + name, - }, - when: { - event: [ 'pull_request' ], - }, - }, { name: 'publish', image: 'plugins/docker:' + os + '-' + arch, diff --git a/router/server.go b/router/server.go new file mode 100644 index 000000000..7dd738e5f --- /dev/null +++ b/router/server.go @@ -0,0 +1,317 @@ +package router + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "os" + "sync" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/metric" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/status" + + api "github.com/appleboy/gin-status-api" + "github.com/gin-contrib/logger" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/golang-queue/queue" + "github.com/mattn/go-isatty" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/thoas/stats" + "golang.org/x/crypto/acme/autocert" +) + +var doOnce sync.Once + +func abortWithError(c *gin.Context, code int, message string) { + c.AbortWithStatusJSON(code, gin.H{ + "code": code, + "message": message, + }) +} + +func rootHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "text": "Welcome to notification server.", + }) +} + +func heartbeatHandler(c *gin.Context) { + c.AbortWithStatus(http.StatusOK) +} + +func versionHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "source": "https://github.com/appleboy/gorush", + "version": GetVersion(), + }) +} + +func pushHandler(cfg *config.ConfYaml, q *queue.Queue) gin.HandlerFunc { + return func(c *gin.Context) { + var form notify.RequestPush + var msg string + + if err := c.ShouldBindWith(&form, binding.JSON); err != nil { + msg = "Missing notifications field." + logx.LogAccess.Debug(err) + abortWithError(c, http.StatusBadRequest, msg) + return + } + + if len(form.Notifications) == 0 { + msg = "Notifications field is empty." + logx.LogAccess.Debug(msg) + abortWithError(c, http.StatusBadRequest, msg) + return + } + + if int64(len(form.Notifications)) > cfg.Core.MaxNotification { + msg = fmt.Sprintf("Number of notifications(%d) over limit(%d)", len(form.Notifications), cfg.Core.MaxNotification) + logx.LogAccess.Debug(msg) + abortWithError(c, http.StatusBadRequest, msg) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + // Deprecated: the CloseNotifier interface predates Go's context package. + // New code should use Request.Context instead. + // Change to context package + <-c.Request.Context().Done() + // Don't send notification after client timeout or disconnected. + // See the following issue for detail information. + // https://github.com/appleboy/gorush/issues/422 + if cfg.Core.Sync { + cancel() + } + }() + + counts, logs := handleNotification(ctx, cfg, form, q) + + c.JSON(http.StatusOK, gin.H{ + "success": "ok", + "counts": counts, + "logs": logs, + }) + } +} + +func configHandler(cfg *config.ConfYaml) gin.HandlerFunc { + return func(c *gin.Context) { + c.YAML(http.StatusCreated, cfg) + } +} + +func metricsHandler(c *gin.Context) { + promhttp.Handler().ServeHTTP(c.Writer, c.Request) +} + +func appStatusHandler(q *queue.Queue) gin.HandlerFunc { + return func(c *gin.Context) { + result := status.App{} + + result.Version = GetVersion() + result.QueueMax = q.Capacity() + result.QueueUsage = q.Usage() + result.TotalCount = status.StatStorage.GetTotalCount() + result.Ios.PushSuccess = status.StatStorage.GetIosSuccess() + result.Ios.PushError = status.StatStorage.GetIosError() + result.Android.PushSuccess = status.StatStorage.GetAndroidSuccess() + result.Android.PushError = status.StatStorage.GetAndroidError() + result.Huawei.PushSuccess = status.StatStorage.GetHuaweiSuccess() + result.Huawei.PushError = status.StatStorage.GetHuaweiError() + + c.JSON(http.StatusOK, result) + } +} + +func sysStatsHandler() gin.HandlerFunc { + return func(c *gin.Context) { + c.JSON(http.StatusOK, status.Stats.Data()) + } +} + +// StatMiddleware response time, status code count, etc. +func StatMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + beginning, recorder := status.Stats.Begin(c.Writer) + c.Next() + status.Stats.End(beginning, stats.WithRecorder(recorder)) + } +} + +func autoTLSServer(cfg *config.ConfYaml, q *queue.Queue) *http.Server { + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(cfg.Core.AutoTLS.Host), + Cache: autocert.DirCache(cfg.Core.AutoTLS.Folder), + } + + return &http.Server{ + Addr: ":https", + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + Handler: routerEngine(cfg, q), + } +} + +func routerEngine(cfg *config.ConfYaml, q *queue.Queue) *gin.Engine { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if cfg.Core.Mode == "debug" { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger() + + isTerm := isatty.IsTerminal(os.Stdout.Fd()) + if isTerm { + log.Logger = log.Output( + zerolog.ConsoleWriter{ + Out: os.Stdout, + NoColor: false, + }, + ) + } + + // Support metrics + doOnce.Do(func() { + m := metric.NewMetrics(func() int { + return q.Usage() + }) + prometheus.MustRegister(m) + }) + + // set server mode + gin.SetMode(cfg.Core.Mode) + + r := gin.New() + + // Global middleware + r.Use(logger.SetLogger( + logger.WithUTC(true), + logger.WithSkipPath([]string{ + cfg.API.HealthURI, + cfg.API.MetricURI, + }), + )) + r.Use(gin.Recovery()) + r.Use(VersionMiddleware()) + r.Use(StatMiddleware()) + + r.GET(cfg.API.StatGoURI, api.GinHandler) + r.GET(cfg.API.StatAppURI, appStatusHandler(q)) + r.GET(cfg.API.ConfigURI, configHandler(cfg)) + r.GET(cfg.API.SysStatURI, sysStatsHandler()) + r.POST(cfg.API.PushURI, pushHandler(cfg, q)) + r.GET(cfg.API.MetricURI, metricsHandler) + r.GET(cfg.API.HealthURI, heartbeatHandler) + r.HEAD(cfg.API.HealthURI, heartbeatHandler) + r.GET("/version", versionHandler) + r.GET("/", rootHandler) + + return r +} + +// markFailedNotification adds failure logs for all tokens in push notification +func markFailedNotification(cfg *config.ConfYaml, notification *notify.PushNotification, reason string) []logx.LogPushEntry { + logx.LogError.Error(reason) + logs := make([]logx.LogPushEntry, 0) + for _, token := range notification.Tokens { + logs = append(logs, logx.GetLogPushEntry(&logx.InputLog{ + ID: notification.ID, + Status: core.FailedPush, + Token: token, + Message: notification.Message, + Platform: notification.Platform, + Error: errors.New(reason), + HideToken: cfg.Log.HideToken, + Format: cfg.Log.Format, + })) + } + + return logs +} + +// HandleNotification add notification to queue list. +func handleNotification(ctx context.Context, cfg *config.ConfYaml, req notify.RequestPush, q *queue.Queue) (int, []logx.LogPushEntry) { + var count int + wg := sync.WaitGroup{} + newNotification := []*notify.PushNotification{} + + if cfg.Core.Sync && !core.IsLocalQueue(core.Queue(cfg.Queue.Engine)) { + cfg.Core.Sync = false + } + + for i := range req.Notifications { + notification := &req.Notifications[i] + switch notification.Platform { + case core.PlatFormIos: + if !cfg.Ios.Enabled { + continue + } + case core.PlatFormAndroid: + if !cfg.Android.Enabled { + continue + } + case core.PlatFormHuawei: + if !cfg.Huawei.Enabled { + continue + } + } + newNotification = append(newNotification, notification) + } + + logs := make([]logx.LogPushEntry, 0, count) + for _, notification := range newNotification { + if cfg.Core.Sync { + wg.Add(1) + } + + if core.IsLocalQueue(core.Queue(cfg.Queue.Engine)) && cfg.Core.Sync { + func(msg *notify.PushNotification, cfg *config.ConfYaml) { + if err := q.QueueTask(func(ctx context.Context) error { + defer wg.Done() + resp, err := notify.SendNotification(msg, cfg) + if err != nil { + return err + } + + // add log + logs = append(logs, resp.Logs...) + + return nil + }); err != nil { + logx.LogError.Error(err) + } + }(notification, cfg) + } else if err := q.Queue(notification); err != nil { + resp := markFailedNotification(cfg, notification, "max capacity reached") + // add log + logs = append(logs, resp...) + wg.Done() + } + + count += len(notification.Tokens) + // Count topic message + if notification.To != "" { + count++ + } + } + + if cfg.Core.Sync { + wg.Wait() + } + + status.StatStorage.AddTotalCount(int64(count)) + + return count, logs +} diff --git a/router/server_lambda.go b/router/server_lambda.go new file mode 100644 index 000000000..0a186da7d --- /dev/null +++ b/router/server_lambda.go @@ -0,0 +1,26 @@ +// +build lambda + +package router + +import ( + "context" + "net/http" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + + "github.com/apex/gateway" + "github.com/golang-queue/queue" +) + +// RunHTTPServer provide run http or https protocol. +func RunHTTPServer(ctx context.Context, cfg *config.ConfYaml, q *queue.Queue, s ...*http.Server) (err error) { + if !cfg.Core.Enabled { + logx.LogAccess.Debug("httpd server is disabled.") + return nil + } + + logx.LogAccess.Info("HTTPD server is running on " + cfg.Core.Port + " port.") + + return gateway.ListenAndServe(cfg.Core.Address+":"+cfg.Core.Port, routerEngine(cfg, q)) +} diff --git a/router/server_normal.go b/router/server_normal.go new file mode 100644 index 000000000..3f52e89d0 --- /dev/null +++ b/router/server_normal.go @@ -0,0 +1,124 @@ +// +build !lambda + +package router + +import ( + "context" + "crypto/tls" + "encoding/base64" + "errors" + "net/http" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + + "github.com/golang-queue/queue" + "golang.org/x/sync/errgroup" +) + +// RunHTTPServer provide run http or https protocol. +func RunHTTPServer(ctx context.Context, cfg *config.ConfYaml, q *queue.Queue, s ...*http.Server) (err error) { + var server *http.Server + + if !cfg.Core.Enabled { + logx.LogAccess.Info("httpd server is disabled.") + return nil + } + + if len(s) == 0 { + server = &http.Server{ + Addr: cfg.Core.Address + ":" + cfg.Core.Port, + Handler: routerEngine(cfg, q), + } + } else { + server = s[0] + } + + logx.LogAccess.Info("HTTPD server is running on " + cfg.Core.Port + " port.") + if cfg.Core.AutoTLS.Enabled { + return startServer(ctx, autoTLSServer(cfg, q), cfg) + } else if cfg.Core.SSL { + config := &tls.Config{ + MinVersion: tls.VersionTLS10, + } + + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + config.Certificates = make([]tls.Certificate, 1) + if cfg.Core.CertPath != "" && cfg.Core.KeyPath != "" { + config.Certificates[0], err = tls.LoadX509KeyPair(cfg.Core.CertPath, cfg.Core.KeyPath) + if err != nil { + logx.LogError.Error("Failed to load https cert file: ", err) + return err + } + } else if cfg.Core.CertBase64 != "" && cfg.Core.KeyBase64 != "" { + cert, err := base64.StdEncoding.DecodeString(cfg.Core.CertBase64) + if err != nil { + logx.LogError.Error("base64 decode error:", err.Error()) + return err + } + key, err := base64.StdEncoding.DecodeString(cfg.Core.KeyBase64) + if err != nil { + logx.LogError.Error("base64 decode error:", err.Error()) + return err + } + if config.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil { + logx.LogError.Error("tls key pair error:", err.Error()) + return err + } + } else { + return errors.New("missing https cert config") + } + + server.TLSConfig = config + } + + return startServer(ctx, server, cfg) +} + +func listenAndServe(ctx context.Context, s *http.Server, cfg *config.ConfYaml) error { + var g errgroup.Group + g.Go(func() error { + <-ctx.Done() + timeout := time.Duration(cfg.Core.ShutdownTimeout) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return s.Shutdown(ctx) + }) + g.Go(func() error { + if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { + return err + } + return nil + }) + return g.Wait() +} + +func listenAndServeTLS(ctx context.Context, s *http.Server, cfg *config.ConfYaml) error { + var g errgroup.Group + g.Go(func() error { + <-ctx.Done() + timeout := time.Duration(cfg.Core.ShutdownTimeout) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return s.Shutdown(ctx) + }) + g.Go(func() error { + if err := s.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { + return err + } + return nil + }) + return g.Wait() +} + +func startServer(ctx context.Context, s *http.Server, cfg *config.ConfYaml) error { + if s.TLSConfig == nil { + return listenAndServe(ctx, s, cfg) + } + + return listenAndServeTLS(ctx, s, cfg) +} diff --git a/router/server_test.go b/router/server_test.go new file mode 100644 index 000000000..0d988e891 --- /dev/null +++ b/router/server_test.go @@ -0,0 +1,679 @@ +package router + +import ( + "context" + "crypto/tls" + "io/ioutil" + "log" + "net/http" + "os" + "runtime" + "testing" + "time" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/notify" + "github.com/appleboy/gorush/status" + + "github.com/appleboy/gofight/v2" + "github.com/buger/jsonparser" + "github.com/gin-gonic/gin" + "github.com/golang-queue/queue" + "github.com/stretchr/testify/assert" +) + +var ( + goVersion = runtime.Version() + q *queue.Queue +) + +func TestMain(m *testing.M) { + cfg := initTest() + if err := status.InitAppStatus(cfg); err != nil { + log.Fatal(err) + } + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + if _, err := notify.InitFCMClient(cfg, ""); err != nil { + log.Fatal(err) + } + + q = queue.NewPool( + int(cfg.Core.WorkerNum), + queue.WithFn(func(ctx context.Context, msg queue.QueuedMessage) error { + _, err := notify.SendNotification(msg, cfg) + return err + }), + queue.WithLogger(logx.QueueLogger()), + ) + + code := m.Run() + defer func() { + q.Release() + os.Exit(code) + }() +} + +func initTest() *config.ConfYaml { + cfg, _ := config.LoadConf() + cfg.Core.Mode = "test" + return cfg +} + +// testRequest is testing url string if server is running +func testRequest(t *testing.T, url string) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{ + Timeout: time.Second * 10, + Transport: tr, + } + + resp, err := client.Get(url) + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("close body err:", err) + } + }() + + assert.NoError(t, err) + + _, ioerr := ioutil.ReadAll(resp.Body) + assert.NoError(t, ioerr) + assert.Equal(t, "200 OK", resp.Status, "should get a 200") +} + +func TestPrintGoRushVersion(t *testing.T) { + SetVersion("3.0.0") + ver := GetVersion() + PrintGoRushVersion() + + assert.Equal(t, "3.0.0", ver) +} + +func TestRunNormalServer(t *testing.T) { + cfg := initTest() + + gin.SetMode(gin.TestMode) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + testRequest(t, "http://localhost:8088/api/stat/go") +} + +func TestRunTLSServer(t *testing.T) { + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8087" + cfg.Core.CertPath = "../certificate/localhost.cert" + cfg.Core.KeyPath = "../certificate/localhost.key" + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + testRequest(t, "https://localhost:8087/api/stat/go") +} + +func TestRunTLSBase64Server(t *testing.T) { + // nolint + cert := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrekNDQWVPZ0F3SUJBZ0lKQUxiWkVEdlVRckZLTUEwR0NTcUdTSWIzRFFFQkJRVUFNQlF4RWpBUUJnTlYKQkFNTUNXeHZZMkZzYUc5emREQWVGdzB4TmpBek1qZ3dNek13TkRGYUZ3MHlOakF6TWpZd016TXdOREZhTUJReApFakFRQmdOVkJBTU1DV3h2WTJGc2FHOXpkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DCmdnRUJBTWoxK3hnNGpWTHpWbkI1ajduMXVsMzBXRUU0QkN6Y05GeGc1QU9CNUg1cSt3amUwWVlpVkZnNlBReXYKR0NpcHFJUlhWUmRWUTFoSFNldW5ZR0tlOGxxM1NiMVg4UFVKMTJ2OXVSYnBTOURLMU93cWs4cnNQRHU2c1ZUTApxS0tnSDFaOHlhenphUzBBYlh1QTVlOWdPL1J6aWpibnBFUCtxdU00ZHVlaU1QVkVKeUxxK0VvSVFZK01NOE1QCjhkWnpMNFhabDd3TDRVc0NON3JQY082VzN0bG5UMGlPM2g5Yy9ZbTJoRmh6K0tOSjlLUlJDdnRQR1pFU2lndEsKYkhzWEgwOTlXRG84di9XcDUvZXZCdy8rSkQwb3B4bUNmSElCQUxIdDl2NTNSdnZzRFoxdDMzUnB1NUM4em5FWQpZMkF5N05neGhxanFvV0pxQTQ4bEplQTBjbHNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkMwYlRVMVhvZmVoCk5LSWVsYXNoSXNxS2lkRFlNQjhHQTFVZEl3UVlNQmFBRkMwYlRVMVhvZmVoTktJZWxhc2hJc3FLaWREWU1Bd0cKQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFBaUpMOElNVHdOWDlYcVFXWURGZ2tHNApBbnJWd1FocmVBcUM5clN4RENqcXFuTUhQSEd6Y0NlRE1MQU1vaDBrT3kyMG5vd1VHTnRDWjB1QnZuWDJxMWJOCmcxanQrR0JjTEpEUjNMTDRDcE5PbG0zWWhPeWN1TmZXTXhUQTdCWGttblNyWkQvN0toQXJzQkVZOGF1bHh3S0oKSFJnTmxJd2Uxb0ZEMVlkWDFCUzVwcDR0MjVCNlZxNEEzRk1NVWtWb1dFNjg4bkUxNjhodlFnd2pySGtnSGh3ZQplTjhsR0UyRGhGcmFYbldtRE1kd2FIRDNIUkZHaHlwcElGTitmN0JxYldYOWdNK1QyWVJUZk9iSVhMV2JxSkxECjNNay9Oa3hxVmNnNGVZNTR3SjF1ZkNVR0FZQUlhWTZmUXFpTlV6OG5od0szdDQ1TkJWVDl5L3VKWHFuVEx5WT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=` + // nolint + key := `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeVBYN0dEaU5Vdk5XY0htUHVmVzZYZlJZUVRnRUxOdzBYR0RrQTRIa2ZtcjdDTjdSCmhpSlVXRG85REs4WUtLbW9oRmRWRjFWRFdFZEo2NmRnWXA3eVdyZEp2VmZ3OVFuWGEvMjVGdWxMME1yVTdDcVQKeXV3OE83cXhWTXVvb3FBZlZuekpyUE5wTFFCdGU0RGw3MkE3OUhPS051ZWtRLzZxNHpoMjU2SXc5VVFuSXVyNApTZ2hCajR3end3L3gxbk12aGRtWHZBdmhTd0kzdXM5dzdwYmUyV2RQU0k3ZUgxejlpYmFFV0hQNG8wbjBwRkVLCiswOFprUktLQzBwc2V4Y2ZUMzFZT2p5Lzlhbm45NjhIRC80a1BTaW5HWUo4Y2dFQXNlMzIvbmRHKyt3Tm5XM2YKZEdtN2tMek9jUmhqWURMczJER0dxT3FoWW1vRGp5VWw0RFJ5V3dJREFRQUJBb0lCQUdUS3FzTjlLYlNmQTQycQpDcUkwVXVMb3VKTU5hMXFzbno1dUFpNllLV2dXZEE0QTQ0bXBFakNtRlJTVmhVSnZ4V3VLK2N5WUlRelh4SVdECkQxNm5aZHFGNzJBZUNXWjlKeVNzdnZaMDBHZktNM3kzNWlSeTA4c0pXZ096bWNMbkdKQ2lTZXlLc1FlM0hUSkMKZGhEWGJYcXZzSFRWUFpnMDFMVGVEeFVpVGZmVThOTUtxUjJBZWNRMnNURHdYRWhBblR5QXRuemwvWGFCZ0Z6dQpVNkc3RnpHTTV5OWJ4a2ZRVmt2eStERUprSEdOT2p6d2NWZkJ5eVZsNjEwaXhtRzF2bXhWajlQYldtSVBzVVY4CnlTbWpodkRRYk9mb3hXMGg5dlRsVHFHdFFjQnc5NjJvc25ERE1XRkNkTTdsek8wVDdSUm5QVkdJUnBDSk9LaHEKa2VxSEt3RUNnWUVBOHd3SS9pWnVnaG9UWFRORzlMblFRL1dBdHNxTzgwRWpNVFVoZW81STFrT3ptVXowOXB5aAppQXNVRG9OMC8yNnRaNVdOamxueVp1N2R2VGMveDNkVFpwbU5ub284Z2NWYlFORUNEUnpxZnVROVBQWG0xU041CjZwZUJxQXZCdjc4aGpWMDVhWHpQRy9WQmJlaWc3bDI5OUVhckVBK2Evb0gzS3JnRG9xVnFFMEVDZ1lFQTA2dkEKWUptZ2c0ZlpSdWNBWW9hWXNMejlaOXJDRmpUZTFQQlRtVUprYk9SOHZGSUhIVFRFV2kvU3V4WEwwd0RTZW9FMgo3QlFtODZnQ0M3L0tnUmRyem9CcVo1cVM5TXYyZHNMZ1k2MzVWU2dqamZaa1ZMaUgxVlJScFNRT2JZbmZveXNnCmdhdGNIU0tNRXhkNFNMUUJ5QXVJbVhQK0w1YXlEQmNFSmZicVNwc0NnWUI3OElzMWIwdXpOTERqT2g3WTlWaHIKRDJxUHpFT1JjSW9Oc2RaY3RPb1h1WGFBbW1uZ3lJYm01UjlaTjFnV1djNDdvRndMVjNyeFdxWGdzNmZtZzhjWAo3djMwOXZGY0M5UTQvVnhhYTRCNUxOSzluM2dUQUlCUFRPdGxVbmwrMm15MXRmQnRCcVJtMFc2SUtiVEhXUzVnCnZ4akVtL0NpRUl5R1VFZ3FUTWdIQVFLQmdCS3VYZFFvdXRuZzYzUXVmd0l6RHRiS1Z6TUxRNFhpTktobWJYcGgKT2F2Q25wK2dQYkIrTDdZbDhsdEFtVFNPSmdWWjBoY1QwRHhBMzYxWngrMk11NThHQmw0T2JsbmNobXdFMXZqMQpLY1F5UHJFUXhkb1VUeWlzd0dmcXZyczhKOWltdmIrejkvVTZUMUtBQjhXaTNXVmlYelByNE1zaWFhUlhnNjQyCkZJZHhBb0dBWjcvNzM1ZGtoSmN5T2ZzK0xLc0xyNjhKU3N0b29yWE9ZdmRNdTErSkdhOWlMdWhuSEVjTVZXQzgKSXVpaHpQZmxvWnRNYkdZa1pKbjhsM0JlR2Q4aG1mRnRnVGdaR1BvVlJldGZ0MkxERkxuUHhwMnNFSDVPRkxzUQpSK0sva0FPdWw4ZVN0V3VNWE9GQTlwTXpHa0dFZ0lGSk1KT3lhSk9OM2tlZFFJOGRlQ009Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==` + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8089" + cfg.Core.CertPath = "" + cfg.Core.KeyPath = "" + cfg.Core.CertBase64 = cert + cfg.Core.KeyBase64 = key + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + testRequest(t, "https://localhost:8089/api/stat/go") +} + +func TestRunAutoTLSServer(t *testing.T) { + cfg := initTest() + cfg.Core.AutoTLS.Enabled = true + ctx, cancel := context.WithCancel(context.Background()) + go func() { + assert.NoError(t, RunHTTPServer(ctx, cfg, q)) + }() + + defer func() { + // close the server + cancel() + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) +} + +func TestLoadTLSCertError(t *testing.T) { + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8087" + cfg.Core.CertPath = "../config/config.yml" + cfg.Core.KeyPath = "../config/config.yml" + + assert.Error(t, RunHTTPServer(context.Background(), cfg, q)) +} + +func TestMissingTLSCertcfgg(t *testing.T) { + cfg := initTest() + + cfg.Core.SSL = true + cfg.Core.Port = "8087" + cfg.Core.CertPath = "" + cfg.Core.KeyPath = "" + cfg.Core.CertBase64 = "" + cfg.Core.KeyBase64 = "" + + err := RunHTTPServer(context.Background(), cfg, q) + assert.Error(t, RunHTTPServer(context.Background(), cfg, q)) + assert.Equal(t, "missing https cert config", err.Error()) +} + +func TestRootHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // log for json + cfg.Log.Format = "json" + + r.GET("/"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "text") + + assert.Equal(t, "Welcome to notification server.", value) + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestAPIStatusGoHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/api/stat/go"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "go_version") + + assert.Equal(t, goVersion, value) + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestAPIStatusAppHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + appVersion := "v1.0.0" + SetVersion(appVersion) + + r.GET("/api/stat/app"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "version") + + assert.Equal(t, appVersion, value) + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestAPIConfigHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/api/config"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusCreated, r.Code) + }) +} + +func TestMissingNotificationsParameter(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // missing notifications parameter. + r.POST("/api/push"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestEmptyNotifications(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // notifications is empty. + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []notify.PushNotification{}, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestMutableContent(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + // notifications is empty. + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []gofight.D{ + { + "tokens": []string{"aaaaa", "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome From API", + "mutable_content": 1, + "topic": "test", + "badge": 1, + "alert": gofight.D{ + "title": "title", + "body": "body", + }, + }, + }, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + // json: cannot unmarshal number into Go struct field notify.PushNotification.mutable_content of type bool + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestOutOfRangeMaxNotifications(t *testing.T) { + cfg := initTest() + + cfg.Core.MaxNotification = int64(1) + + r := gofight.New() + + // notifications is empty. + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []gofight.D{ + { + "tokens": []string{"aaaaa", "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome API From Android", + }, + { + "tokens": []string{"aaaaa", "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome API From Android", + }, + }, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusBadRequest, r.Code) + }) +} + +func TestSuccessPushHandler(t *testing.T) { + t.Skip() + cfg := initTest() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + r := gofight.New() + + r.POST("/api/push"). + SetJSON(gofight.D{ + "notifications": []gofight.D{ + { + "tokens": []string{androidToken, "bbbbb"}, + "platform": core.PlatFormAndroid, + "message": "Welcome Android", + }, + }, + }). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestSysStatsHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/sys/stats"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestMetricsHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/metrics"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestGETHeartbeatHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.GET("/healthz"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestHEADHeartbeatHandler(t *testing.T) { + cfg := initTest() + + r := gofight.New() + + r.HEAD("/healthz"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + }) +} + +func TestVersionHandler(t *testing.T) { + SetVersion("3.0.0") + cfg := initTest() + + r := gofight.New() + + r.GET("/version"). + Run(routerEngine(cfg, q), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { + assert.Equal(t, http.StatusOK, r.Code) + data := r.Body.Bytes() + + value, _ := jsonparser.GetString(data, "version") + + assert.Equal(t, "3.0.0", value) + }) +} + +func TestDisabledHTTPServer(t *testing.T) { + cfg := initTest() + cfg.Core.Enabled = false + err := RunHTTPServer(context.Background(), cfg, q) + cfg.Core.Enabled = true + + assert.Nil(t, err) +} + +func TestSenMultipleNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{"11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, + Platform: core.PlatFormIos, + Message: "Welcome iOS", + }, + // android + { + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 3, count) + assert.Equal(t, 0, len(logs)) +} + +func TestDisabledAndroidNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = false + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{"11aa01229f15f0f0c5209d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"}, + Platform: core.PlatFormIos, + Message: "Welcome iOS", + }, + // android + { + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 1, count) + assert.Equal(t, 0, len(logs)) +} + +func TestSyncModeForNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = true + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + // enable sync mode + cfg.Core.Sync = true + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{ + "11aa01229f15f0f0c12029d8c111d1ae1f2365f14cebc4af26cd6d76b7919ef7", + }, + Platform: core.PlatFormIos, + Message: "Welcome iOS Sync", + }, + // android + { + Tokens: []string{androidToken, "bbbbb"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android Sync", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 3, count) + assert.Equal(t, 2, len(logs)) +} + +func TestSyncModeForTopicNotification(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Log.HideToken = false + + // enable sync mode + cfg.Core.Sync = true + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // android + { + // error:InvalidParameters + // Check that the provided parameters have the right name and type. + To: "/topics/foo-bar@@@##", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + // android + { + // success + To: "/topics/foo-bar", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + // android + { + // success + Condition: "'dogs' in topics || 'cats' in topics", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Topic Message!", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 2, count) + assert.Equal(t, 1, len(logs)) +} + +func TestSyncModeForDeviceGroupNotification(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + cfg.Log.HideToken = false + + // enable sync mode + cfg.Core.Sync = true + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // android + { + To: "aUniqueKey", + Platform: core.PlatFormAndroid, + Message: "This is a Firebase Cloud Messaging Device Group Message!", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 1, count) + assert.Equal(t, 1, len(logs)) +} + +func TestDisabledIosNotifications(t *testing.T) { + ctx := context.Background() + cfg := initTest() + + cfg.Ios.Enabled = false + cfg.Ios.KeyPath = "../certificate/certificate-valid.pem" + err := notify.InitAPNSClient(cfg) + assert.Nil(t, err) + + cfg.Android.Enabled = true + cfg.Android.APIKey = os.Getenv("ANDROID_API_KEY") + + androidToken := os.Getenv("ANDROID_TEST_TOKEN") + + req := notify.RequestPush{ + Notifications: []notify.PushNotification{ + // ios + { + Tokens: []string{"11aa01229f15f0f0c52021d8cf3cd0ae1f2365fe4cebc4af26cd6d76b7919ef7"}, + Platform: core.PlatFormIos, + Message: "Welcome iOS platform", + }, + // android + { + Tokens: []string{androidToken, androidToken + "_"}, + Platform: core.PlatFormAndroid, + Message: "Welcome Android platform", + }, + }, + } + + count, logs := handleNotification(ctx, cfg, req, q) + assert.Equal(t, 2, count) + assert.Equal(t, 0, len(logs)) +} diff --git a/gorush/version.go b/router/version.go similarity index 97% rename from gorush/version.go rename to router/version.go index f8cdcdffa..6300ec346 100644 --- a/gorush/version.go +++ b/router/version.go @@ -1,4 +1,4 @@ -package gorush +package router import ( "fmt" diff --git a/rpc/client_grpc_health.go b/rpc/client_grpc_health.go index 98c498056..39fce0281 100644 --- a/rpc/client_grpc_health.go +++ b/rpc/client_grpc_health.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // generate protobuffs @@ -41,7 +42,7 @@ func (c *healthClient) Check(ctx context.Context) (bool, error) { } return false, nil } - switch grpc.Code(err) { + switch status.Code(err) { case codes.Aborted, codes.DataLoss, diff --git a/rpc/example/go/health/main.go b/rpc/example/go/health/main.go index 323b8a4fd..91d66f2d2 100644 --- a/rpc/example/go/health/main.go +++ b/rpc/example/go/health/main.go @@ -8,6 +8,7 @@ import ( "github.com/appleboy/gorush/rpc" "google.golang.org/grpc" + "google.golang.org/grpc/status" ) const ( @@ -27,7 +28,7 @@ func main() { for { ok, err := client.Check(context.Background()) if !ok || err != nil { - log.Printf("can't connect grpc server: %v, code: %v\n", err, grpc.Code(err)) + log.Printf("can't connect grpc server: %v, code: %v\n", err, status.Code(err)) } else { log.Println("connect the grpc server successfully") } diff --git a/rpc/example/go/send/main.go b/rpc/example/go/send/main.go index 9ead8edae..f9b0581bd 100644 --- a/rpc/example/go/send/main.go +++ b/rpc/example/go/send/main.go @@ -30,7 +30,7 @@ func main() { Badge: 1, Category: "test", Sound: "test", - Priority: proto.Priority_High, + Priority: proto.NotificationRequest_HIGH, Alert: &proto.Alert{ Title: "Test Title", Body: "Test Alert Body", @@ -50,8 +50,11 @@ func main() { }, }) if err != nil { - log.Fatalf("could not greet: %v", err) + log.Println("could not greet: ", err) + } + + if r != nil { + log.Printf("Success: %t\n", r.Success) + log.Printf("Count: %d\n", r.Counts) } - log.Printf("Success: %t\n", r.Success) - log.Printf("Count: %d\n", r.Counts) } diff --git a/rpc/example/node/gorush_pb.js b/rpc/example/node/gorush_pb.js index 32aa1b7e4..bf5d084c6 100644 --- a/rpc/example/node/gorush_pb.js +++ b/rpc/example/node/gorush_pb.js @@ -7,6 +7,8 @@ * @public */ // GENERATED CODE -- DO NOT EDIT! +/* eslint-disable */ +// @ts-nocheck var jspb = require('google-protobuf'); var goog = jspb; @@ -20,7 +22,7 @@ goog.exportSymbol('proto.proto.HealthCheckResponse', null, global); goog.exportSymbol('proto.proto.HealthCheckResponse.ServingStatus', null, global); goog.exportSymbol('proto.proto.NotificationReply', null, global); goog.exportSymbol('proto.proto.NotificationRequest', null, global); -goog.exportSymbol('proto.proto.Priority', null, global); +goog.exportSymbol('proto.proto.NotificationRequest.Priority', null, global); /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -725,7 +727,7 @@ proto.proto.NotificationRequest.deserializeBinaryFromReader = function(msg, read msg.setImage(value); break; case 16: - var value = /** @type {!proto.proto.Priority} */ (reader.readEnum()); + var value = /** @type {!proto.proto.NotificationRequest.Priority} */ (reader.readEnum()); msg.setPriority(value); break; default: @@ -874,6 +876,14 @@ proto.proto.NotificationRequest.serializeBinaryToWriter = function(message, writ }; +/** + * @enum {number} + */ +proto.proto.NotificationRequest.Priority = { + NORMAL: 0, + HIGH: 1 +}; + /** * repeated string tokens = 1; * @return {!Array} @@ -1202,16 +1212,16 @@ proto.proto.NotificationRequest.prototype.setImage = function(value) { /** - * optional Priority Priority = 16; - * @return {!proto.proto.Priority} + * optional Priority priority = 16; + * @return {!proto.proto.NotificationRequest.Priority} */ proto.proto.NotificationRequest.prototype.getPriority = function() { - return /** @type {!proto.proto.Priority} */ (jspb.Message.getFieldWithDefault(this, 16, 0)); + return /** @type {!proto.proto.NotificationRequest.Priority} */ (jspb.Message.getFieldWithDefault(this, 16, 0)); }; /** - * @param {!proto.proto.Priority} value + * @param {!proto.proto.NotificationRequest.Priority} value * @return {!proto.proto.NotificationRequest} returns this */ proto.proto.NotificationRequest.prototype.setPriority = function(value) { @@ -1648,12 +1658,4 @@ proto.proto.HealthCheckResponse.prototype.setStatus = function(value) { }; -/** - * @enum {number} - */ -proto.proto.Priority = { - NORMAL: 0, - HIGH: 1 -}; - goog.object.extend(exports, proto.proto); diff --git a/rpc/example/node/package-lock.json b/rpc/example/node/package-lock.json index a13e67077..e7a86febe 100644 --- a/rpc/example/node/package-lock.json +++ b/rpc/example/node/package-lock.json @@ -4,37 +4,80 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + } + }, "@types/bytebuffer": { - "version": "5.0.40", - "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.40.tgz", - "integrity": "sha512-h48dyzZrPMz25K6Q4+NCwWaxwXany2FhQg/ErOcdZS1ZpsaDnDMZg8JYLMTGz7uvXKrcKGJUZJlZObyfgdaN9g==", + "version": "5.0.42", + "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.42.tgz", + "integrity": "sha512-lEgKojWUAc/MG2t649oZS5AfYFP2xRNPoDuwDBlBMjHXd8MaGPgFgtCXUK7inZdBOygmVf10qxc1Us8GXC96aw==", "requires": { - "@types/long": "4.0.0", - "@types/node": "12.11.7" + "@types/long": "*", + "@types/node": "*" } }, "@types/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "12.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.7.tgz", - "integrity": "sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==" + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz", + "integrity": "sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "ascli": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", "requires": { - "colour": "0.7.1", - "optjs": "3.2.2" + "colour": "~0.7.1", + "optjs": "~3.2.2" } }, "async": { @@ -43,16 +86,16 @@ "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -61,7 +104,7 @@ "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", "requires": { - "long": "3.2.0" + "long": "~3" } }, "camelcase": { @@ -69,14 +112,19 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "code-point-at": { @@ -94,32 +142,83 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "dom-walk": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, "glob": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", - "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "global": { @@ -137,487 +236,30 @@ "integrity": "sha512-d0cMO8TJ6xtB/WrVHCv5U81L2ulX+aCD58IljyAN6mHwdHHJ2jbcauX5glvivi3s3hx7EYEo7eUA9WftzamMnw==" }, "grpc": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.1.tgz", - "integrity": "sha512-NFIWbt3RXZU4VlDLpiAM/Ca8Yz30QShUdPGMqOPH652PmA+2fau2vuW+tOYWQUkYMfBW2yege/T5p65e5TetVQ==", - "requires": { - "@types/bytebuffer": "5.0.40", - "lodash.camelcase": "4.3.0", - "lodash.clone": "4.5.0", - "nan": "2.14.0", - "node-pre-gyp": "0.13.0", - "protobufjs": "5.0.3" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "2.1.2" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "fs-minipass": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", - "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", - "requires": { - "minipass": "2.3.5" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.3" - } - }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", - "requires": { - "minipass": "2.3.5" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "requires": { - "debug": "3.2.6", - "iconv-lite": "0.4.24", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", - "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.4.0", - "nopt": "4.0.1", - "npm-packlist": "1.4.4", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.7.1", - "semver": "5.7.1", - "tar": "4.4.10" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" - }, - "npm-packlist": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", - "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.6" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "isarray": "1.0.0", - "process-nextick-args": "2.0.1", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "7.1.4" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "tar": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", - "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", - "requires": { - "chownr": "1.1.2", - "fs-minipass": "1.2.6", - "minipass": "2.3.5", - "minizlib": "1.2.1", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" - } + "version": "1.24.9", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.9.tgz", + "integrity": "sha512-BOq1AJocZJcG/6qyX3LX2KvKy91RIix10GFLhqWg+1L6b73uWIN2w0cq+lSi0q9mXfkjeFaBz83+oau7oJqG3Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.4", + "@types/bytebuffer": "^5.0.40", + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "protobufjs": "^5.0.3" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" } }, "inflight": { @@ -625,8 +267,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -644,15 +286,20 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "lodash": { @@ -675,6 +322,29 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -688,7 +358,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -696,22 +366,78 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "optjs": { @@ -724,7 +450,7 @@ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "path-is-absolute": { @@ -737,25 +463,83 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "protobufjs": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", "requires": { - "ascli": "1.0.1", - "bytebuffer": "5.0.1", - "glob": "7.1.5", - "yargs": "3.32.0" + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -763,7 +547,33 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" + } + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" } }, "window-size": { @@ -776,8 +586,8 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "wrappy": { @@ -786,22 +596,27 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "3.32.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "requires": { - "camelcase": "2.1.1", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "os-locale": "1.4.0", - "string-width": "1.0.2", - "window-size": "0.1.4", - "y18n": "3.2.1" + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" } } } diff --git a/rpc/example/node/package.json b/rpc/example/node/package.json index 839809179..44ff5305c 100644 --- a/rpc/example/node/package.json +++ b/rpc/example/node/package.json @@ -5,7 +5,7 @@ "async": "^3.1.0", "global": "^4.4.0", "google-protobuf": "^3.10.0", - "grpc": "^1.24.1", + "grpc": "^1.24.9", "lodash": "^4.17.21", "minimist": ">=1.2.2" } diff --git a/rpc/proto/gorush.pb.go b/rpc/proto/gorush.pb.go index 24392f1bb..e22570f2a 100644 --- a/rpc/proto/gorush.pb.go +++ b/rpc/proto/gorush.pb.go @@ -1,23 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.12.3 +// protoc-gen-go v1.25.0-devel +// protoc v3.14.0 // source: gorush.proto package proto import ( - context "context" reflect "reflect" sync "sync" - proto "github.com/golang/protobuf/proto" - _struct "github.com/golang/protobuf/ptypes/struct" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" ) const ( @@ -27,54 +22,50 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - -type Priority int32 +type NotificationRequest_Priority int32 const ( - Priority_Normal Priority = 0 - Priority_High Priority = 1 + NotificationRequest_NORMAL NotificationRequest_Priority = 0 + NotificationRequest_HIGH NotificationRequest_Priority = 1 ) -// Enum value maps for Priority. +// Enum value maps for NotificationRequest_Priority. var ( - Priority_name = map[int32]string{ - 0: "Normal", - 1: "High", + NotificationRequest_Priority_name = map[int32]string{ + 0: "NORMAL", + 1: "HIGH", } - Priority_value = map[string]int32{ - "Normal": 0, - "High": 1, + NotificationRequest_Priority_value = map[string]int32{ + "NORMAL": 0, + "HIGH": 1, } ) -func (x Priority) Enum() *Priority { - p := new(Priority) +func (x NotificationRequest_Priority) Enum() *NotificationRequest_Priority { + p := new(NotificationRequest_Priority) *p = x return p } -func (x Priority) String() string { +func (x NotificationRequest_Priority) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (Priority) Descriptor() protoreflect.EnumDescriptor { +func (NotificationRequest_Priority) Descriptor() protoreflect.EnumDescriptor { return file_gorush_proto_enumTypes[0].Descriptor() } -func (Priority) Type() protoreflect.EnumType { +func (NotificationRequest_Priority) Type() protoreflect.EnumType { return &file_gorush_proto_enumTypes[0] } -func (x Priority) Number() protoreflect.EnumNumber { +func (x NotificationRequest_Priority) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } -// Deprecated: Use Priority.Descriptor instead. -func (Priority) EnumDescriptor() ([]byte, []int) { - return file_gorush_proto_rawDescGZIP(), []int{0} +// Deprecated: Use NotificationRequest_Priority.Descriptor instead. +func (NotificationRequest_Priority) EnumDescriptor() ([]byte, []int) { + return file_gorush_proto_rawDescGZIP(), []int{1, 0} } type HealthCheckResponse_ServingStatus int32 @@ -250,22 +241,22 @@ type NotificationRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Tokens []string `protobuf:"bytes,1,rep,name=tokens,proto3" json:"tokens,omitempty"` - Platform int32 `protobuf:"varint,2,opt,name=platform,proto3" json:"platform,omitempty"` - Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` - Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` - Topic string `protobuf:"bytes,5,opt,name=topic,proto3" json:"topic,omitempty"` - Key string `protobuf:"bytes,6,opt,name=key,proto3" json:"key,omitempty"` - Badge int32 `protobuf:"varint,7,opt,name=badge,proto3" json:"badge,omitempty"` - Category string `protobuf:"bytes,8,opt,name=category,proto3" json:"category,omitempty"` - Alert *Alert `protobuf:"bytes,9,opt,name=alert,proto3" json:"alert,omitempty"` - Sound string `protobuf:"bytes,10,opt,name=sound,proto3" json:"sound,omitempty"` - ContentAvailable bool `protobuf:"varint,11,opt,name=contentAvailable,proto3" json:"contentAvailable,omitempty"` - ThreadID string `protobuf:"bytes,12,opt,name=threadID,proto3" json:"threadID,omitempty"` - MutableContent bool `protobuf:"varint,13,opt,name=mutableContent,proto3" json:"mutableContent,omitempty"` - Data *_struct.Struct `protobuf:"bytes,14,opt,name=data,proto3" json:"data,omitempty"` - Image string `protobuf:"bytes,15,opt,name=image,proto3" json:"image,omitempty"` - Priority Priority `protobuf:"varint,16,opt,name=Priority,proto3,enum=proto.Priority" json:"Priority,omitempty"` + Tokens []string `protobuf:"bytes,1,rep,name=tokens,proto3" json:"tokens,omitempty"` + Platform int32 `protobuf:"varint,2,opt,name=platform,proto3" json:"platform,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + Topic string `protobuf:"bytes,5,opt,name=topic,proto3" json:"topic,omitempty"` + Key string `protobuf:"bytes,6,opt,name=key,proto3" json:"key,omitempty"` + Badge int32 `protobuf:"varint,7,opt,name=badge,proto3" json:"badge,omitempty"` + Category string `protobuf:"bytes,8,opt,name=category,proto3" json:"category,omitempty"` + Alert *Alert `protobuf:"bytes,9,opt,name=alert,proto3" json:"alert,omitempty"` + Sound string `protobuf:"bytes,10,opt,name=sound,proto3" json:"sound,omitempty"` + ContentAvailable bool `protobuf:"varint,11,opt,name=contentAvailable,proto3" json:"contentAvailable,omitempty"` + ThreadID string `protobuf:"bytes,12,opt,name=threadID,proto3" json:"threadID,omitempty"` + MutableContent bool `protobuf:"varint,13,opt,name=mutableContent,proto3" json:"mutableContent,omitempty"` + Data *structpb.Struct `protobuf:"bytes,14,opt,name=data,proto3" json:"data,omitempty"` + Image string `protobuf:"bytes,15,opt,name=image,proto3" json:"image,omitempty"` + Priority NotificationRequest_Priority `protobuf:"varint,16,opt,name=priority,proto3,enum=proto.NotificationRequest_Priority" json:"priority,omitempty"` } func (x *NotificationRequest) Reset() { @@ -391,7 +382,7 @@ func (x *NotificationRequest) GetMutableContent() bool { return false } -func (x *NotificationRequest) GetData() *_struct.Struct { +func (x *NotificationRequest) GetData() *structpb.Struct { if x != nil { return x.Data } @@ -405,11 +396,11 @@ func (x *NotificationRequest) GetImage() string { return "" } -func (x *NotificationRequest) GetPriority() Priority { +func (x *NotificationRequest) GetPriority() NotificationRequest_Priority { if x != nil { return x.Priority } - return Priority_Normal + return NotificationRequest_NORMAL } type NotificationReply struct { @@ -585,7 +576,7 @@ var file_gorush_proto_rawDesc = []byte{ 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x22, 0xed, 0x03, 0x0a, 0x13, 0x4e, 0x6f, + 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x41, 0x72, 0x67, 0x73, 0x22, 0xa3, 0x04, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, @@ -613,39 +604,40 @@ var file_gorush_proto_rawDesc = []byte{ 0x61, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x08, - 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, - 0x08, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x45, 0x0a, 0x11, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, - 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x22, 0x93, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, 0x0a, 0x0d, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, - 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, - 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x2a, 0x20, 0x0a, 0x08, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x10, 0x00, 0x12, 0x08, - 0x0a, 0x04, 0x48, 0x69, 0x67, 0x68, 0x10, 0x01, 0x32, 0x48, 0x0a, 0x06, 0x47, 0x6f, 0x72, 0x75, - 0x73, 0x68, 0x12, 0x3e, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, + 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x08, + 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x20, 0x0a, + 0x08, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, + 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x01, 0x22, + 0x45, 0x0a, 0x11, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x3a, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, + 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, + 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x32, 0x48, 0x0a, 0x06, + 0x47, 0x6f, 0x72, 0x75, 0x73, 0x68, 0x12, 0x3e, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x22, 0x00, 0x32, 0x48, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x3e, 0x0a, 0x05, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x48, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x12, 0x3e, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -664,21 +656,21 @@ var ( file_gorush_proto_enumTypes = make([]protoimpl.EnumInfo, 2) file_gorush_proto_msgTypes = make([]protoimpl.MessageInfo, 5) file_gorush_proto_goTypes = []interface{}{ - (Priority)(0), // 0: proto.Priority + (NotificationRequest_Priority)(0), // 0: proto.NotificationRequest.Priority (HealthCheckResponse_ServingStatus)(0), // 1: proto.HealthCheckResponse.ServingStatus (*Alert)(nil), // 2: proto.Alert (*NotificationRequest)(nil), // 3: proto.NotificationRequest (*NotificationReply)(nil), // 4: proto.NotificationReply (*HealthCheckRequest)(nil), // 5: proto.HealthCheckRequest (*HealthCheckResponse)(nil), // 6: proto.HealthCheckResponse - (*_struct.Struct)(nil), // 7: google.protobuf.Struct + (*structpb.Struct)(nil), // 7: google.protobuf.Struct } ) var file_gorush_proto_depIdxs = []int32{ 2, // 0: proto.NotificationRequest.alert:type_name -> proto.Alert 7, // 1: proto.NotificationRequest.data:type_name -> google.protobuf.Struct - 0, // 2: proto.NotificationRequest.Priority:type_name -> proto.Priority + 0, // 2: proto.NotificationRequest.priority:type_name -> proto.NotificationRequest.Priority 1, // 3: proto.HealthCheckResponse.status:type_name -> proto.HealthCheckResponse.ServingStatus 3, // 4: proto.Gorush.Send:input_type -> proto.NotificationRequest 5, // 5: proto.Health.Check:input_type -> proto.HealthCheckRequest @@ -778,155 +770,3 @@ func file_gorush_proto_init() { file_gorush_proto_goTypes = nil file_gorush_proto_depIdxs = nil } - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ context.Context - _ grpc.ClientConnInterface -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// GorushClient is the client API for Gorush service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type GorushClient interface { - Send(ctx context.Context, in *NotificationRequest, opts ...grpc.CallOption) (*NotificationReply, error) -} - -type gorushClient struct { - cc grpc.ClientConnInterface -} - -func NewGorushClient(cc grpc.ClientConnInterface) GorushClient { - return &gorushClient{cc} -} - -func (c *gorushClient) Send(ctx context.Context, in *NotificationRequest, opts ...grpc.CallOption) (*NotificationReply, error) { - out := new(NotificationReply) - err := c.cc.Invoke(ctx, "/proto.Gorush/Send", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// GorushServer is the server API for Gorush service. -type GorushServer interface { - Send(context.Context, *NotificationRequest) (*NotificationReply, error) -} - -// UnimplementedGorushServer can be embedded to have forward compatible implementations. -type UnimplementedGorushServer struct{} - -func (*UnimplementedGorushServer) Send(context.Context, *NotificationRequest) (*NotificationReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") -} - -func RegisterGorushServer(s *grpc.Server, srv GorushServer) { - s.RegisterService(&_Gorush_serviceDesc, srv) -} - -func _Gorush_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(NotificationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(GorushServer).Send(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/proto.Gorush/Send", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GorushServer).Send(ctx, req.(*NotificationRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Gorush_serviceDesc = grpc.ServiceDesc{ - ServiceName: "proto.Gorush", - HandlerType: (*GorushServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Send", - Handler: _Gorush_Send_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "gorush.proto", -} - -// HealthClient is the client API for Health service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type HealthClient interface { - Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) -} - -type healthClient struct { - cc grpc.ClientConnInterface -} - -func NewHealthClient(cc grpc.ClientConnInterface) HealthClient { - return &healthClient{cc} -} - -func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { - out := new(HealthCheckResponse) - err := c.cc.Invoke(ctx, "/proto.Health/Check", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// HealthServer is the server API for Health service. -type HealthServer interface { - Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) -} - -// UnimplementedHealthServer can be embedded to have forward compatible implementations. -type UnimplementedHealthServer struct{} - -func (*UnimplementedHealthServer) Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Check not implemented") -} - -func RegisterHealthServer(s *grpc.Server, srv HealthServer) { - s.RegisterService(&_Health_serviceDesc, srv) -} - -func _Health_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(HealthCheckRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HealthServer).Check(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/proto.Health/Check", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HealthServer).Check(ctx, req.(*HealthCheckRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Health_serviceDesc = grpc.ServiceDesc{ - ServiceName: "proto.Health", - HandlerType: (*HealthServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Check", - Handler: _Health_Check_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "gorush.proto", -} diff --git a/rpc/proto/gorush.proto b/rpc/proto/gorush.proto index 25238c780..7181c66e9 100644 --- a/rpc/proto/gorush.proto +++ b/rpc/proto/gorush.proto @@ -33,10 +33,10 @@ message NotificationRequest { google.protobuf.Struct data = 14; string image = 15; enum Priority { - Normal = 0; - High = 1; + NORMAL = 0; + HIGH = 1; } - Priority Priority = 16; + Priority priority = 16; } message NotificationReply { diff --git a/rpc/proto/gorush_grpc.pb.go b/rpc/proto/gorush_grpc.pb.go new file mode 100644 index 000000000..767f79828 --- /dev/null +++ b/rpc/proto/gorush_grpc.pb.go @@ -0,0 +1,182 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package proto + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GorushClient is the client API for Gorush service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GorushClient interface { + Send(ctx context.Context, in *NotificationRequest, opts ...grpc.CallOption) (*NotificationReply, error) +} + +type gorushClient struct { + cc grpc.ClientConnInterface +} + +func NewGorushClient(cc grpc.ClientConnInterface) GorushClient { + return &gorushClient{cc} +} + +func (c *gorushClient) Send(ctx context.Context, in *NotificationRequest, opts ...grpc.CallOption) (*NotificationReply, error) { + out := new(NotificationReply) + err := c.cc.Invoke(ctx, "/proto.Gorush/Send", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GorushServer is the server API for Gorush service. +// All implementations should embed UnimplementedGorushServer +// for forward compatibility +type GorushServer interface { + Send(context.Context, *NotificationRequest) (*NotificationReply, error) +} + +// UnimplementedGorushServer should be embedded to have forward compatible implementations. +type UnimplementedGorushServer struct{} + +func (UnimplementedGorushServer) Send(context.Context, *NotificationRequest) (*NotificationReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") +} + +// UnsafeGorushServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GorushServer will +// result in compilation errors. +type UnsafeGorushServer interface { + mustEmbedUnimplementedGorushServer() +} + +func RegisterGorushServer(s grpc.ServiceRegistrar, srv GorushServer) { + s.RegisterService(&Gorush_ServiceDesc, srv) +} + +func _Gorush_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NotificationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GorushServer).Send(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Gorush/Send", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GorushServer).Send(ctx, req.(*NotificationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Gorush_ServiceDesc is the grpc.ServiceDesc for Gorush service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Gorush_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Gorush", + HandlerType: (*GorushServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Send", + Handler: _Gorush_Send_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gorush.proto", +} + +// HealthClient is the client API for Health service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HealthClient interface { + Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) +} + +type healthClient struct { + cc grpc.ClientConnInterface +} + +func NewHealthClient(cc grpc.ClientConnInterface) HealthClient { + return &healthClient{cc} +} + +func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + out := new(HealthCheckResponse) + err := c.cc.Invoke(ctx, "/proto.Health/Check", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HealthServer is the server API for Health service. +// All implementations should embed UnimplementedHealthServer +// for forward compatibility +type HealthServer interface { + Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) +} + +// UnimplementedHealthServer should be embedded to have forward compatible implementations. +type UnimplementedHealthServer struct{} + +func (UnimplementedHealthServer) Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Check not implemented") +} + +// UnsafeHealthServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HealthServer will +// result in compilation errors. +type UnsafeHealthServer interface { + mustEmbedUnimplementedHealthServer() +} + +func RegisterHealthServer(s grpc.ServiceRegistrar, srv HealthServer) { + s.RegisterService(&Health_ServiceDesc, srv) +} + +func _Health_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HealthServer).Check(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Health/Check", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HealthServer).Check(ctx, req.(*HealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Health_ServiceDesc is the grpc.ServiceDesc for Health service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Health_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Health", + HandlerType: (*HealthServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Check", + Handler: _Health_Check_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gorush.proto", +} diff --git a/rpc/server.go b/rpc/server.go index e6b6b8e92..d68831523 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -6,7 +6,10 @@ import ( "strings" "sync" - "github.com/appleboy/gorush/gorush" + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/core" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/notify" "github.com/appleboy/gorush/rpc/proto" "google.golang.org/grpc" @@ -17,14 +20,16 @@ import ( // Server is used to implement gorush grpc server. type Server struct { - mu sync.Mutex + cfg *config.ConfYaml + mu sync.Mutex // statusMap stores the serving status of the services this Server monitors. statusMap map[string]proto.HealthCheckResponse_ServingStatus } // NewServer returns a new Server. -func NewServer() *Server { +func NewServer(cfg *config.ConfYaml) *Server { return &Server{ + cfg: cfg, statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus), } } @@ -50,7 +55,7 @@ func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*prot // Send implements helloworld.GreeterServer func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*proto.NotificationReply, error) { badge := int(in.Badge) - notification := gorush.PushNotification{ + notification := notify.PushNotification{ Platform: int(in.Platform), Tokens: in.Tokens, Message: in.Message, @@ -70,12 +75,12 @@ func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*prot notification.Badge = &badge } - if in.Topic != "" && in.Platform == gorush.PlatFormAndroid { + if in.Topic != "" && in.Platform == core.PlatFormAndroid { notification.To = in.Topic } if in.Alert != nil { - notification.Alert = gorush.Alert{ + notification.Alert = notify.Alert{ Title: in.Alert.Title, Body: in.Alert.Body, Subtitle: in.Alert.Subtitle, @@ -96,7 +101,12 @@ func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*prot } } - go gorush.SendNotification(ctx, notification) + go func() { + _, err := notify.SendNotification(¬ification, s.cfg) + if err != nil { + logx.LogError.Error(err) + } + }() return &proto.NotificationReply{ Success: true, @@ -105,35 +115,33 @@ func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*prot } // RunGRPCServer run gorush grpc server -func RunGRPCServer(ctx context.Context) error { - if !gorush.PushConf.GRPC.Enabled { - gorush.LogAccess.Info("gRPC server is disabled.") +func RunGRPCServer(ctx context.Context, cfg *config.ConfYaml) error { + if !cfg.GRPC.Enabled { + logx.LogAccess.Info("gRPC server is disabled.") return nil } s := grpc.NewServer() - rpcSrv := NewServer() + rpcSrv := NewServer(cfg) proto.RegisterGorushServer(s, rpcSrv) proto.RegisterHealthServer(s, rpcSrv) // Register reflection service on gRPC server. reflection.Register(s) - lis, err := net.Listen("tcp", ":"+gorush.PushConf.GRPC.Port) + lis, err := net.Listen("tcp", ":"+cfg.GRPC.Port) if err != nil { - gorush.LogError.Fatalln(err) + logx.LogError.Fatalln(err) return err } - gorush.LogAccess.Info("gRPC server is running on " + gorush.PushConf.GRPC.Port + " port.") + logx.LogAccess.Info("gRPC server is running on " + cfg.GRPC.Port + " port.") go func() { - select { - case <-ctx.Done(): - s.GracefulStop() // graceful shutdown - gorush.LogAccess.Info("shutdown the gRPC server") - } + <-ctx.Done() + s.GracefulStop() // graceful shutdown + logx.LogAccess.Info("shutdown the gRPC server") }() if err = s.Serve(lis); err != nil { - gorush.LogError.Fatalln(err) + logx.LogError.Fatalln(err) } return err } diff --git a/rpc/server_test.go b/rpc/server_test.go index 173637b5c..5d72061ce 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/appleboy/gorush/gorush" + "github.com/appleboy/gorush/config" "google.golang.org/grpc" "google.golang.org/grpc/connectivity" @@ -12,17 +12,22 @@ import ( const gRPCAddr = "localhost:9000" +func initTest() *config.ConfYaml { + cfg, _ := config.LoadConf() + cfg.Core.Mode = "test" + return cfg +} + func TestGracefulShutDownGRPCServer(t *testing.T) { - // server configs - gorush.InitLog() - gorush.PushConf.GRPC.Enabled = true - gorush.PushConf.GRPC.Port = "9000" - gorush.PushConf.Log.Format = "json" + cfg := initTest() + cfg.GRPC.Enabled = true + cfg.GRPC.Port = "9000" + cfg.Log.Format = "json" // Run gRPC server ctx, gRPCContextCancel := context.WithCancel(context.Background()) go func() { - if err := RunGRPCServer(ctx); err != nil { + if err := RunGRPCServer(ctx, cfg); err != nil { panic(err) } }() diff --git a/status/status.go b/status/status.go new file mode 100644 index 000000000..68b423d7d --- /dev/null +++ b/status/status.go @@ -0,0 +1,84 @@ +package status + +import ( + "errors" + + "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/logx" + "github.com/appleboy/gorush/storage" + "github.com/appleboy/gorush/storage/badger" + "github.com/appleboy/gorush/storage/boltdb" + "github.com/appleboy/gorush/storage/buntdb" + "github.com/appleboy/gorush/storage/leveldb" + "github.com/appleboy/gorush/storage/memory" + "github.com/appleboy/gorush/storage/redis" + + "github.com/thoas/stats" +) + +// Stats provide response time, status code count, etc. +var Stats *stats.Stats + +// StatStorage implements the storage interface +var StatStorage storage.Storage + +// App is status structure +type App struct { + Version string `json:"version"` + QueueMax int `json:"queue_max"` + QueueUsage int `json:"queue_usage"` + TotalCount int64 `json:"total_count"` + Ios IosStatus `json:"ios"` + Android AndroidStatus `json:"android"` + Huawei HuaweiStatus `json:"huawei"` +} + +// AndroidStatus is android structure +type AndroidStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// IosStatus is iOS structure +type IosStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// HuaweiStatus is huawei structure +type HuaweiStatus struct { + PushSuccess int64 `json:"push_success"` + PushError int64 `json:"push_error"` +} + +// InitAppStatus for initialize app status +func InitAppStatus(conf *config.ConfYaml) error { + logx.LogAccess.Info("Init App Status Engine as ", conf.Stat.Engine) + switch conf.Stat.Engine { + case "memory": + StatStorage = memory.New() + case "redis": + StatStorage = redis.New(conf) + case "boltdb": + StatStorage = boltdb.New(conf) + case "buntdb": + StatStorage = buntdb.New(conf) + case "leveldb": + StatStorage = leveldb.New(conf) + case "badger": + StatStorage = badger.New(conf) + default: + logx.LogError.Error("storage error: can't find storage driver") + return errors.New("can't find storage driver") + } + + if err := StatStorage.Init(); err != nil { + logx.LogError.Error("storage error: " + err.Error()) + + return err + } + + Stats = stats.New() + + return nil +} diff --git a/gorush/status_test.go b/status/status_test.go similarity index 84% rename from gorush/status_test.go rename to status/status_test.go index 3f59f4a76..b057f4341 100644 --- a/gorush/status_test.go +++ b/status/status_test.go @@ -1,15 +1,23 @@ -package gorush +package status import ( + "os" "testing" "time" + "github.com/appleboy/gorush/config" + "github.com/stretchr/testify/assert" ) +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + func TestStorageDriverExist(t *testing.T) { - PushConf.Stat.Engine = "Test" - err := InitAppStatus() + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "Test" + err := InitAppStatus(cfg) assert.Error(t, err) } @@ -18,8 +26,9 @@ func TestStatForMemoryEngine(t *testing.T) { time.Sleep(5 * time.Second) var val int64 - PushConf.Stat.Engine = "memory" - err := InitAppStatus() + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "memory" + err := InitAppStatus(cfg) assert.Nil(t, err) StatStorage.AddTotalCount(100) @@ -41,31 +50,34 @@ func TestStatForMemoryEngine(t *testing.T) { } func TestRedisServerSuccess(t *testing.T) { - PushConf.Stat.Engine = "redis" - PushConf.Stat.Redis.Addr = "redis:6379" + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "redis" + cfg.Stat.Redis.Addr = "redis:6379" - err := InitAppStatus() + err := InitAppStatus(cfg) assert.NoError(t, err) } func TestRedisServerError(t *testing.T) { - PushConf.Stat.Engine = "redis" - PushConf.Stat.Redis.Addr = "redis:6370" + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "redis" + cfg.Stat.Redis.Addr = "redis:6370" - err := InitAppStatus() + err := InitAppStatus(cfg) assert.Error(t, err) } func TestStatForRedisEngine(t *testing.T) { var val int64 - PushConf.Stat.Engine = "redis" - PushConf.Stat.Redis.Addr = "redis:6379" - err := InitAppStatus() + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "redis" + cfg.Stat.Redis.Addr = "redis:6379" + err := InitAppStatus(cfg) assert.Nil(t, err) - StatStorage.Init() + assert.Nil(t, StatStorage.Init()) StatStorage.Reset() StatStorage.AddTotalCount(100) @@ -89,7 +101,8 @@ func TestStatForRedisEngine(t *testing.T) { func TestDefaultEngine(t *testing.T) { var val int64 // defaul engine as memory - err := InitAppStatus() + cfg, _ := config.LoadConf() + err := InitAppStatus(cfg) assert.Nil(t, err) StatStorage.Reset() @@ -114,8 +127,10 @@ func TestDefaultEngine(t *testing.T) { func TestStatForBoltDBEngine(t *testing.T) { var val int64 - PushConf.Stat.Engine = "boltdb" - InitAppStatus() + cfg, _ := config.LoadConf() + cfg.Stat.Engine = "boltdb" + err := InitAppStatus(cfg) + assert.Nil(t, err) StatStorage.Reset() @@ -139,7 +154,7 @@ func TestStatForBoltDBEngine(t *testing.T) { // func TestStatForBuntDBEngine(t *testing.T) { // var val int64 -// PushConf.Stat.Engine = "buntdb" +// cfg.Stat.Engine = "buntdb" // err := InitAppStatus() // assert.Nil(t, err) @@ -165,7 +180,7 @@ func TestStatForBoltDBEngine(t *testing.T) { // func TestStatForLevelDBEngine(t *testing.T) { // var val int64 -// PushConf.Stat.Engine = "leveldb" +// cfg.Stat.Engine = "leveldb" // err := InitAppStatus() // assert.Nil(t, err) @@ -191,7 +206,7 @@ func TestStatForBoltDBEngine(t *testing.T) { // func TestStatForBadgerEngine(t *testing.T) { // var val int64 -// PushConf.Stat.Engine = "badger" +// cfg.Stat.Engine = "badger" // err := InitAppStatus() // assert.Nil(t, err) diff --git a/storage/badger/badger.go b/storage/badger/badger.go index 254a810d9..b0b02d1e2 100644 --- a/storage/badger/badger.go +++ b/storage/badger/badger.go @@ -1,7 +1,6 @@ package badger import ( - "fmt" "log" "os" "strconv" @@ -9,11 +8,11 @@ import ( "github.com/appleboy/gorush/config" "github.com/appleboy/gorush/storage" - "github.com/dgraph-io/badger/v2" + "github.com/dgraph-io/badger/v3" ) // New func implements the storage interface for gorush (https://github.com/appleboy/gorush) -func New(config config.ConfYaml) *Storage { +func New(config *config.ConfYaml) *Storage { return &Storage{ config: config, } @@ -21,7 +20,7 @@ func New(config config.ConfYaml) *Storage { // Storage is interface structure type Storage struct { - config config.ConfYaml + config *config.ConfYaml opts badger.Options name string db *badger.DB @@ -84,7 +83,7 @@ func (s *Storage) getBadger(key string, count *int64) { return err } - i, err := strconv.ParseInt(fmt.Sprintf("%s", val), 10, 64) + i, err := strconv.ParseInt(string(val), 10, 64) if err != nil { return err } diff --git a/storage/badger/badger_test.go b/storage/badger/badger_test.go index 033e11d10..3c26b270c 100644 --- a/storage/badger/badger_test.go +++ b/storage/badger/badger_test.go @@ -3,16 +3,16 @@ package badger import ( "testing" - c "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/config" "github.com/stretchr/testify/assert" ) func TestBadgerEngine(t *testing.T) { var val int64 - config, _ := c.LoadConf("") + cfg, _ := config.LoadConf() - badger := New(config) + badger := New(cfg) err := badger.Init() assert.Nil(t, err) badger.Reset() diff --git a/storage/boltdb/boltdb.go b/storage/boltdb/boltdb.go index b1f3425cd..1ae26a942 100644 --- a/storage/boltdb/boltdb.go +++ b/storage/boltdb/boltdb.go @@ -10,7 +10,7 @@ import ( ) // New func implements the storage interface for gorush (https://github.com/appleboy/gorush) -func New(config config.ConfYaml) *Storage { +func New(config *config.ConfYaml) *Storage { return &Storage{ config: config, } @@ -18,7 +18,7 @@ func New(config config.ConfYaml) *Storage { // Storage is interface structure type Storage struct { - config config.ConfYaml + config *config.ConfYaml db *storm.DB } diff --git a/storage/boltdb/boltdb_test.go b/storage/boltdb/boltdb_test.go index 8ea2670f6..07e16873b 100644 --- a/storage/boltdb/boltdb_test.go +++ b/storage/boltdb/boltdb_test.go @@ -3,16 +3,16 @@ package boltdb import ( "testing" - c "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/config" "github.com/stretchr/testify/assert" ) func TestBoltDBEngine(t *testing.T) { var val int64 - config, _ := c.LoadConf("") + cfg, _ := config.LoadConf() - boltDB := New(config) + boltDB := New(cfg) err := boltDB.Init() assert.Nil(t, err) boltDB.Reset() diff --git a/storage/buntdb/buntdb.go b/storage/buntdb/buntdb.go index b09c0bb12..5bcd30ddb 100644 --- a/storage/buntdb/buntdb.go +++ b/storage/buntdb/buntdb.go @@ -12,7 +12,7 @@ import ( ) // New func implements the storage interface for gorush (https://github.com/appleboy/gorush) -func New(config config.ConfYaml) *Storage { +func New(config *config.ConfYaml) *Storage { return &Storage{ config: config, } @@ -20,7 +20,7 @@ func New(config config.ConfYaml) *Storage { // Storage is interface structure type Storage struct { - config config.ConfYaml + config *config.ConfYaml db *buntdb.DB } diff --git a/storage/buntdb/buntdb_test.go b/storage/buntdb/buntdb_test.go index 5a65f0331..022ce4214 100644 --- a/storage/buntdb/buntdb_test.go +++ b/storage/buntdb/buntdb_test.go @@ -4,21 +4,21 @@ import ( "os" "testing" - c "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/config" "github.com/stretchr/testify/assert" ) func TestBuntDBEngine(t *testing.T) { var val int64 - config, _ := c.LoadConf("") + cfg, _ := config.LoadConf() - if _, err := os.Stat(config.Stat.BuntDB.Path); os.IsNotExist(err) { - err := os.RemoveAll(config.Stat.BuntDB.Path) + if _, err := os.Stat(cfg.Stat.BuntDB.Path); os.IsNotExist(err) { + err := os.RemoveAll(cfg.Stat.BuntDB.Path) assert.Nil(t, err) } - buntDB := New(config) + buntDB := New(cfg) err := buntDB.Init() assert.Nil(t, err) buntDB.Reset() diff --git a/storage/leveldb/leveldb.go b/storage/leveldb/leveldb.go index 82d36015e..8164fc062 100644 --- a/storage/leveldb/leveldb.go +++ b/storage/leveldb/leveldb.go @@ -21,7 +21,7 @@ func (s *Storage) getLevelDB(key string, count *int64) { } // New func implements the storage interface for gorush (https://github.com/appleboy/gorush) -func New(config config.ConfYaml) *Storage { +func New(config *config.ConfYaml) *Storage { return &Storage{ config: config, } @@ -29,7 +29,7 @@ func New(config config.ConfYaml) *Storage { // Storage is interface structure type Storage struct { - config config.ConfYaml + config *config.ConfYaml db *leveldb.DB } diff --git a/storage/leveldb/leveldb_test.go b/storage/leveldb/leveldb_test.go index aa6918939..826821481 100644 --- a/storage/leveldb/leveldb_test.go +++ b/storage/leveldb/leveldb_test.go @@ -4,21 +4,21 @@ import ( "os" "testing" - c "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/config" "github.com/stretchr/testify/assert" ) func TestLevelDBEngine(t *testing.T) { var val int64 - config, _ := c.LoadConf("") + cfg, _ := config.LoadConf() - if _, err := os.Stat(config.Stat.LevelDB.Path); os.IsNotExist(err) { - err = os.RemoveAll(config.Stat.LevelDB.Path) + if _, err := os.Stat(cfg.Stat.LevelDB.Path); os.IsNotExist(err) { + err = os.RemoveAll(cfg.Stat.LevelDB.Path) assert.Nil(t, err) } - levelDB := New(config) + levelDB := New(cfg) err := levelDB.Init() assert.Nil(t, err) levelDB.Reset() diff --git a/storage/memory/memory.go b/storage/memory/memory.go index 7c0e96139..4b3c5ff20 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go @@ -4,7 +4,7 @@ import ( "sync/atomic" ) -// StatusApp is app status structure +// statApp is app status structure type statApp struct { TotalCount int64 `json:"total_count"` Ios IosStatus `json:"ios"` diff --git a/storage/redis/redis.go b/storage/redis/redis.go index 7df20e409..45001a1f8 100644 --- a/storage/redis/redis.go +++ b/storage/redis/redis.go @@ -1,97 +1,119 @@ package redis import ( + "context" + "fmt" + "reflect" "strconv" + "strings" "github.com/appleboy/gorush/config" "github.com/appleboy/gorush/storage" - "github.com/go-redis/redis/v7" + "github.com/go-redis/redis/v8" ) // New func implements the storage interface for gorush (https://github.com/appleboy/gorush) -func New(config config.ConfYaml) *Storage { +func New(config *config.ConfYaml) *Storage { return &Storage{ + ctx: context.Background(), config: config, } } func (s *Storage) getInt64(key string, count *int64) { - val, _ := s.client.Get(key).Result() + val, _ := s.client.Get(s.ctx, key).Result() *count, _ = strconv.ParseInt(val, 10, 64) } // Storage is interface structure type Storage struct { - config config.ConfYaml - client *redis.Client + ctx context.Context + config *config.ConfYaml + client redis.Cmdable } // Init client storage. func (s *Storage) Init() error { - s.client = redis.NewClient(&redis.Options{ - Addr: s.config.Stat.Redis.Addr, - Password: s.config.Stat.Redis.Password, - DB: s.config.Stat.Redis.DB, - }) - _, err := s.client.Ping().Result() + if s.config.Stat.Redis.Cluster { + s.client = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: strings.Split(s.config.Stat.Redis.Addr, ","), + Password: s.config.Stat.Redis.Password, + }) + } else { + s.client = redis.NewClient(&redis.Options{ + Addr: s.config.Stat.Redis.Addr, + Password: s.config.Stat.Redis.Password, + DB: s.config.Stat.Redis.DB, + }) + } + + if err := s.client.Ping(s.ctx).Err(); err != nil { + return err + } - return err + return nil } // Close the storage connection func (s *Storage) Close() error { - if s.client == nil { + switch v := s.client.(type) { + case *redis.Client: + return v.Close() + case *redis.ClusterClient: + return v.Close() + case nil: return nil + default: + // this will not happen anyway, unless we mishandle it on `Init` + panic(fmt.Sprintf("invalid redis client: %v", reflect.TypeOf(v))) } - - return s.client.Close() } // Reset Client storage. func (s *Storage) Reset() { - s.client.Set(storage.TotalCountKey, int64(0), 0) - s.client.Set(storage.IosSuccessKey, int64(0), 0) - s.client.Set(storage.IosErrorKey, int64(0), 0) - s.client.Set(storage.AndroidSuccessKey, int64(0), 0) - s.client.Set(storage.AndroidErrorKey, int64(0), 0) - s.client.Set(storage.HuaweiSuccessKey, int64(0), 0) - s.client.Set(storage.HuaweiErrorKey, int64(0), 0) + s.client.Set(s.ctx, storage.TotalCountKey, int64(0), 0) + s.client.Set(s.ctx, storage.IosSuccessKey, int64(0), 0) + s.client.Set(s.ctx, storage.IosErrorKey, int64(0), 0) + s.client.Set(s.ctx, storage.AndroidSuccessKey, int64(0), 0) + s.client.Set(s.ctx, storage.AndroidErrorKey, int64(0), 0) + s.client.Set(s.ctx, storage.HuaweiSuccessKey, int64(0), 0) + s.client.Set(s.ctx, storage.HuaweiErrorKey, int64(0), 0) } // AddTotalCount record push notification count. func (s *Storage) AddTotalCount(count int64) { - s.client.IncrBy(storage.TotalCountKey, count) + s.client.IncrBy(s.ctx, storage.TotalCountKey, count) } // AddIosSuccess record counts of success iOS push notification. func (s *Storage) AddIosSuccess(count int64) { - s.client.IncrBy(storage.IosSuccessKey, count) + s.client.IncrBy(s.ctx, storage.IosSuccessKey, count) } // AddIosError record counts of error iOS push notification. func (s *Storage) AddIosError(count int64) { - s.client.IncrBy(storage.IosErrorKey, count) + s.client.IncrBy(s.ctx, storage.IosErrorKey, count) } // AddAndroidSuccess record counts of success Android push notification. func (s *Storage) AddAndroidSuccess(count int64) { - s.client.IncrBy(storage.AndroidSuccessKey, count) + s.client.IncrBy(s.ctx, storage.AndroidSuccessKey, count) } // AddAndroidError record counts of error Android push notification. func (s *Storage) AddAndroidError(count int64) { - s.client.IncrBy(storage.AndroidErrorKey, count) + s.client.IncrBy(s.ctx, storage.AndroidErrorKey, count) } // AddHuaweiSuccess record counts of success Android push notification. func (s *Storage) AddHuaweiSuccess(count int64) { - s.client.IncrBy(storage.HuaweiSuccessKey, count) + s.client.IncrBy(s.ctx, storage.HuaweiSuccessKey, count) } // AddHuaweiError record counts of error Android push notification. func (s *Storage) AddHuaweiError(count int64) { - s.client.IncrBy(storage.HuaweiErrorKey, count) + s.client.IncrBy(s.ctx, storage.HuaweiErrorKey, count) } // GetTotalCount show counts of all notification. diff --git a/storage/redis/redis_test.go b/storage/redis/redis_test.go index 3075df796..0f3107206 100644 --- a/storage/redis/redis_test.go +++ b/storage/redis/redis_test.go @@ -4,15 +4,15 @@ import ( "sync" "testing" - c "github.com/appleboy/gorush/config" + "github.com/appleboy/gorush/config" "github.com/stretchr/testify/assert" ) func TestRedisServerError(t *testing.T) { - config, _ := c.LoadConf("") - config.Stat.Redis.Addr = "redis:6370" + cfg, _ := config.LoadConf() + cfg.Stat.Redis.Addr = "redis:6370" - redis := New(config) + redis := New(cfg) err := redis.Init() assert.Error(t, err) @@ -21,10 +21,10 @@ func TestRedisServerError(t *testing.T) { func TestRedisEngine(t *testing.T) { var val int64 - config, _ := c.LoadConf("") - config.Stat.Redis.Addr = "redis:6379" + cfg, _ := config.LoadConf() + cfg.Stat.Redis.Addr = "redis:6379" - redis := New(config) + redis := New(cfg) err := redis.Init() assert.Nil(t, err) redis.Reset()