Merge pull request #55 from appleboy/stat
Fix #54 Support success or failure of notification counts information.
This commit is contained in:
commit
5483769d14
74
README.md
74
README.md
|
@ -19,6 +19,7 @@ A push notification server using [Gin](https://github.com/gin-gonic/gin) framewo
|
||||||
* Support zero downtime restarts for go servers using [endless](https://github.com/fvbock/endless).
|
* Support zero downtime restarts for go servers using [endless](https://github.com/fvbock/endless).
|
||||||
* Support [HTTP/2](https://http2.github.io/) or HTTP/1.1 protocol.
|
* Support [HTTP/2](https://http2.github.io/) or HTTP/1.1 protocol.
|
||||||
* Support notification queue and multiple workers.
|
* Support notification queue and multiple workers.
|
||||||
|
* Support `/api/stat/app` show notification success and failure counts.
|
||||||
|
|
||||||
See the [YAML config example](config/config.yml):
|
See the [YAML config example](config/config.yml):
|
||||||
|
|
||||||
|
@ -35,7 +36,8 @@ core:
|
||||||
|
|
||||||
api:
|
api:
|
||||||
push_uri: "/api/push"
|
push_uri: "/api/push"
|
||||||
stat_go_uri: "/api/status"
|
stat_go_uri: "/api/stat/go"
|
||||||
|
stat_app_uri: "/api/stat/app"
|
||||||
|
|
||||||
android:
|
android:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
@ -101,19 +103,79 @@ Please make sure your [config.yml](config/config.yml) exist. Default port is `80
|
||||||
$ gorush -c config.yml
|
$ gorush -c config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
Test status of api server using [httpie](https://github.com/jkbrzt/httpie) tool:
|
Get go status of api server using [httpie](https://github.com/jkbrzt/httpie) tool:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http -v --verify=no --json GET https://localhost:8088/api/status
|
$ http -v --verify=no --json GET https://localhost:8088/api/stat/go
|
||||||
```
|
```
|
||||||
|
|
||||||
## Web API
|
## Web API
|
||||||
|
|
||||||
gorush support the following API.
|
Gorush support the following API.
|
||||||
|
|
||||||
* **GET** `/api/status` Golang cpu, memory, gc, etc information. Thanks for [golang-stats-api-handler](https://github.com/fukata/golang-stats-api-handler).
|
* **GET** `/api/stat/go` Golang cpu, memory, gc, etc information. Thanks for [golang-stats-api-handler](https://github.com/fukata/golang-stats-api-handler).
|
||||||
|
* **GET** `/api/stat/app` show notification success and failure counts.
|
||||||
* **POST** `/api/push` push ios and android notifications.
|
* **POST** `/api/push` push ios and android notifications.
|
||||||
|
|
||||||
|
### GET /api/stat/go
|
||||||
|
|
||||||
|
Golang cpu, memory, gc, etc information. Response with `200` http status code.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
time: 1460686815848046600,
|
||||||
|
go_version: "go1.6.1",
|
||||||
|
go_os: "darwin",
|
||||||
|
go_arch: "amd64",
|
||||||
|
cpu_num: 4,
|
||||||
|
goroutine_num: 15,
|
||||||
|
gomaxprocs: 4,
|
||||||
|
cgo_call_num: 1,
|
||||||
|
memory_alloc: 7455192,
|
||||||
|
memory_total_alloc: 8935464,
|
||||||
|
memory_sys: 12560632,
|
||||||
|
memory_lookups: 17,
|
||||||
|
memory_mallocs: 31426,
|
||||||
|
memory_frees: 11772,
|
||||||
|
memory_stack: 524288,
|
||||||
|
heap_alloc: 7455192,
|
||||||
|
heap_sys: 8912896,
|
||||||
|
heap_idle: 909312,
|
||||||
|
heap_inuse: 8003584,
|
||||||
|
heap_released: 0,
|
||||||
|
heap_objects: 19654,
|
||||||
|
gc_next: 9754725,
|
||||||
|
gc_last: 1460686815762559700,
|
||||||
|
gc_num: 2,
|
||||||
|
gc_per_second: 0,
|
||||||
|
gc_pause_per_second: 0,
|
||||||
|
gc_pause: [
|
||||||
|
0.326576,
|
||||||
|
0.227096
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/stat/app
|
||||||
|
|
||||||
|
Get success or failure of notification counts information.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
queue_max: 8192,
|
||||||
|
queue_usage: 0,
|
||||||
|
total_count: 77,
|
||||||
|
ios: {
|
||||||
|
push_success: 19,
|
||||||
|
push_error: 38
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
push_success: 10,
|
||||||
|
push_error: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### POST /api/push
|
### POST /api/push
|
||||||
|
|
||||||
Simple send iOS notification example, the `platform` value is `1`:
|
Simple send iOS notification example, the `platform` value is `1`:
|
||||||
|
@ -356,7 +418,7 @@ $ docker run -name gorush -p 80:8088 appleboy/gorush
|
||||||
Testing your gorush server.
|
Testing your gorush server.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http -v --verify=no --json GET http://your.docker.host/api/status
|
$ http -v --verify=no --json GET http://your.docker.host/api/stat/go
|
||||||
```
|
```
|
||||||
|
|
||||||
![statue screenshot](screenshot/status.png)
|
![statue screenshot](screenshot/status.png)
|
||||||
|
|
|
@ -10,7 +10,8 @@ core:
|
||||||
|
|
||||||
api:
|
api:
|
||||||
push_uri: "/api/push"
|
push_uri: "/api/push"
|
||||||
stat_go_uri: "/api/status"
|
stat_go_uri: "/api/stat/go"
|
||||||
|
stat_app_uri: "/api/stat/app"
|
||||||
|
|
||||||
android:
|
android:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -77,6 +77,7 @@ func main() {
|
||||||
Message: *message,
|
Message: *message,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gorush.InitAppStatus()
|
||||||
gorush.PushToAndroid(req)
|
gorush.PushToAndroid(req)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -103,6 +104,7 @@ func main() {
|
||||||
Message: *message,
|
Message: *message,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gorush.InitAppStatus()
|
||||||
gorush.InitAPNSClient()
|
gorush.InitAPNSClient()
|
||||||
gorush.PushToIOS(req)
|
gorush.PushToIOS(req)
|
||||||
|
|
||||||
|
@ -113,6 +115,7 @@ func main() {
|
||||||
gorush.LogError.Fatal(err)
|
gorush.LogError.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gorush.InitAppStatus()
|
||||||
gorush.InitAPNSClient()
|
gorush.InitAPNSClient()
|
||||||
gorush.InitWorkers(gorush.PushConf.Core.WorkerNum, gorush.PushConf.Core.QueueNum)
|
gorush.InitWorkers(gorush.PushConf.Core.WorkerNum, gorush.PushConf.Core.QueueNum)
|
||||||
gorush.RunHTTPServer()
|
gorush.RunHTTPServer()
|
||||||
|
|
|
@ -31,6 +31,7 @@ type SectionCore struct {
|
||||||
type SectionAPI struct {
|
type SectionAPI struct {
|
||||||
PushURI string `yaml:"push_uri"`
|
PushURI string `yaml:"push_uri"`
|
||||||
StatGoURI string `yaml:"stat_go_uri"`
|
StatGoURI string `yaml:"stat_go_uri"`
|
||||||
|
StatAppURI string `yaml:"stat_app_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SectionAndroid is sub seciont of config.
|
// SectionAndroid is sub seciont of config.
|
||||||
|
@ -72,7 +73,8 @@ func BuildDefaultPushConf() ConfYaml {
|
||||||
|
|
||||||
// Api
|
// Api
|
||||||
conf.API.PushURI = "/api/push"
|
conf.API.PushURI = "/api/push"
|
||||||
conf.API.StatGoURI = "/api/status"
|
conf.API.StatGoURI = "/api/stat/go"
|
||||||
|
conf.API.StatAppURI = "/api/stat/app"
|
||||||
|
|
||||||
// Android
|
// Android
|
||||||
conf.Android.Enabled = false
|
conf.Android.Enabled = false
|
||||||
|
|
|
@ -19,4 +19,5 @@ var (
|
||||||
LogAccess *logrus.Logger
|
LogAccess *logrus.Logger
|
||||||
// LogError is log server error log
|
// LogError is log server error log
|
||||||
LogError *logrus.Logger
|
LogError *logrus.Logger
|
||||||
|
RushStatus StatusApp
|
||||||
)
|
)
|
||||||
|
|
|
@ -158,9 +158,11 @@ func queueNotification(req RequestPush) int {
|
||||||
}
|
}
|
||||||
QueueNotification <- notification
|
QueueNotification <- notification
|
||||||
|
|
||||||
count++
|
count += len(notification.Tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addTotalCount(int64(count))
|
||||||
|
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +278,7 @@ func PushToIOS(req PushNotification) bool {
|
||||||
// apns server error
|
// apns server error
|
||||||
LogPush(FailedPush, token, req, err)
|
LogPush(FailedPush, token, req, err)
|
||||||
isError = true
|
isError = true
|
||||||
|
addIosError(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,11 +286,13 @@ func PushToIOS(req PushNotification) bool {
|
||||||
// error message:
|
// error message:
|
||||||
// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
|
// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
|
||||||
LogPush(FailedPush, token, req, errors.New(res.Reason))
|
LogPush(FailedPush, token, req, errors.New(res.Reason))
|
||||||
|
addIosError(1)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Sent() {
|
if res.Sent() {
|
||||||
LogPush(SucceededPush, token, req, nil)
|
LogPush(SucceededPush, token, req, nil)
|
||||||
|
addIosSuccess(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +385,8 @@ func PushToAndroid(req PushNotification) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
|
LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
|
||||||
|
addAndroidSuccess(int64(res.Success))
|
||||||
|
addAndroidError(int64(res.Failure))
|
||||||
|
|
||||||
for k, result := range res.Results {
|
for k, result := range res.Results {
|
||||||
if result.Error != "" {
|
if result.Error != "" {
|
||||||
|
|
|
@ -355,7 +355,7 @@ func TestSenMultipleNotifications(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
count := queueNotification(req)
|
count := queueNotification(req)
|
||||||
assert.Equal(t, 2, count)
|
assert.Equal(t, 3, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDisabledAndroidNotifications(t *testing.T) {
|
func TestDisabledAndroidNotifications(t *testing.T) {
|
||||||
|
@ -421,7 +421,7 @@ func TestDisabledIosNotifications(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
count := queueNotification(req)
|
count := queueNotification(req)
|
||||||
assert.Equal(t, 1, count)
|
assert.Equal(t, 2, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingIosCertificate(t *testing.T) {
|
func TestMissingIosCertificate(t *testing.T) {
|
||||||
|
|
|
@ -68,6 +68,7 @@ func routerEngine() *gin.Engine {
|
||||||
r.Use(LogMiddleware())
|
r.Use(LogMiddleware())
|
||||||
|
|
||||||
r.GET(PushConf.API.StatGoURI, api.StatusHandler)
|
r.GET(PushConf.API.StatGoURI, api.StatusHandler)
|
||||||
|
r.GET(PushConf.API.StatAppURI, appStatusHandler)
|
||||||
r.POST(PushConf.API.PushURI, pushHandler)
|
r.POST(PushConf.API.PushURI, pushHandler)
|
||||||
r.GET("/", rootHandler)
|
r.GET("/", rootHandler)
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ func TestRunNormalServer(t *testing.T) {
|
||||||
time.Sleep(5 * time.Millisecond)
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
assert.Error(t, RunHTTPServer())
|
assert.Error(t, RunHTTPServer())
|
||||||
gofight.TestRequest(t, "http://localhost:8088/api/status")
|
gofight.TestRequest(t, "http://localhost:8088/api/stat/go")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunTLSServer(t *testing.T) {
|
func TestRunTLSServer(t *testing.T) {
|
||||||
|
@ -76,12 +76,12 @@ func TestRootHandler(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIStatusHandler(t *testing.T) {
|
func TestAPIStatusGoHandler(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
|
|
||||||
r := gofight.New()
|
r := gofight.New()
|
||||||
|
|
||||||
r.GET("/api/status").
|
r.GET("/api/stat/go").
|
||||||
Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
|
Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
|
||||||
data := []byte(r.Body.String())
|
data := []byte(r.Body.String())
|
||||||
|
|
||||||
|
@ -92,6 +92,17 @@ func TestAPIStatusHandler(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIStatusAppHandler(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
|
||||||
|
r := gofight.New()
|
||||||
|
|
||||||
|
r.GET("/api/stat/app").
|
||||||
|
Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
|
||||||
|
assert.Equal(t, http.StatusOK, r.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestMissingNotificationsParameter(t *testing.T) {
|
func TestMissingNotificationsParameter(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package gorush
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatusApp struct {
|
||||||
|
QueueMax int `json:"queue_max"`
|
||||||
|
QueueUsage int `json:"queue_usage"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
Ios IosStatus `json:"ios"`
|
||||||
|
Android AndroidStatus `json:"android"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AndroidStatus struct {
|
||||||
|
PushSuccess int64 `json:"push_success"`
|
||||||
|
PushError int64 `json:"push_error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IosStatus struct {
|
||||||
|
PushSuccess int64 `json:"push_success"`
|
||||||
|
PushError int64 `json:"push_error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitAppStatus() {
|
||||||
|
RushStatus.TotalCount = 0
|
||||||
|
RushStatus.Ios.PushSuccess = 0
|
||||||
|
RushStatus.Ios.PushError = 0
|
||||||
|
RushStatus.Android.PushSuccess = 0
|
||||||
|
RushStatus.Android.PushError = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTotalCount(count int64) {
|
||||||
|
atomic.AddInt64(&RushStatus.TotalCount, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addIosSuccess(count int64) {
|
||||||
|
atomic.AddInt64(&RushStatus.Ios.PushSuccess, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addIosError(count int64) {
|
||||||
|
atomic.AddInt64(&RushStatus.Ios.PushError, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAndroidSuccess(count int64) {
|
||||||
|
atomic.AddInt64(&RushStatus.Android.PushSuccess, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAndroidError(count int64) {
|
||||||
|
atomic.AddInt64(&RushStatus.Android.PushError, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appStatusHandler(c *gin.Context) {
|
||||||
|
result := StatusApp{}
|
||||||
|
|
||||||
|
result.QueueMax = cap(QueueNotification)
|
||||||
|
result.QueueUsage = len(QueueNotification)
|
||||||
|
result.TotalCount = atomic.LoadInt64(&RushStatus.TotalCount)
|
||||||
|
result.Ios.PushSuccess = atomic.LoadInt64(&RushStatus.Ios.PushSuccess)
|
||||||
|
result.Ios.PushError = atomic.LoadInt64(&RushStatus.Ios.PushError)
|
||||||
|
result.Android.PushSuccess = atomic.LoadInt64(&RushStatus.Android.PushSuccess)
|
||||||
|
result.Android.PushError = atomic.LoadInt64(&RushStatus.Android.PushError)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, result)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package gorush
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddTotalCount(t *testing.T) {
|
||||||
|
InitAppStatus()
|
||||||
|
addTotalCount(1000)
|
||||||
|
|
||||||
|
val := atomic.LoadInt64(&RushStatus.TotalCount)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1000), val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddIosSuccess(t *testing.T) {
|
||||||
|
InitAppStatus()
|
||||||
|
addIosSuccess(1000)
|
||||||
|
|
||||||
|
val := atomic.LoadInt64(&RushStatus.Ios.PushSuccess)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1000), val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddIosError(t *testing.T) {
|
||||||
|
InitAppStatus()
|
||||||
|
addIosError(1000)
|
||||||
|
|
||||||
|
val := atomic.LoadInt64(&RushStatus.Ios.PushError)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1000), val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAndroidSuccess(t *testing.T) {
|
||||||
|
InitAppStatus()
|
||||||
|
addAndroidSuccess(1000)
|
||||||
|
|
||||||
|
val := atomic.LoadInt64(&RushStatus.Android.PushSuccess)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1000), val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddAndroidError(t *testing.T) {
|
||||||
|
InitAppStatus()
|
||||||
|
addAndroidError(1000)
|
||||||
|
|
||||||
|
val := atomic.LoadInt64(&RushStatus.Android.PushError)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1000), val)
|
||||||
|
}
|
Loading…
Reference in New Issue