chore: create router package (#586)
This commit is contained in:
216
router/server.go
Normal file
216
router/server.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/appleboy/gorush/config"
|
||||
"github.com/appleboy/gorush/gorush"
|
||||
"github.com/appleboy/gorush/logx"
|
||||
"github.com/appleboy/gorush/metric"
|
||||
"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/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 isTerm bool
|
||||
|
||||
func init() {
|
||||
// Support metrics
|
||||
m := metric.NewMetrics(func() int {
|
||||
return len(gorush.QueueNotification)
|
||||
})
|
||||
prometheus.MustRegister(m)
|
||||
isTerm = isatty.IsTerminal(os.Stdout.Fd())
|
||||
}
|
||||
|
||||
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) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var form gorush.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 := gorush.HandleNotification(ctx, form)
|
||||
|
||||
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(c *gin.Context) {
|
||||
result := status.App{}
|
||||
|
||||
result.Version = GetVersion()
|
||||
result.QueueMax = cap(gorush.QueueNotification)
|
||||
result.QueueUsage = len(gorush.QueueNotification)
|
||||
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) *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),
|
||||
}
|
||||
}
|
||||
|
||||
func routerEngine(cfg config.ConfYaml) *gin.Engine {
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
if cfg.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(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)
|
||||
r.GET(cfg.API.ConfigURI, configHandler(cfg))
|
||||
r.GET(cfg.API.SysStatURI, sysStatsHandler())
|
||||
r.POST(cfg.API.PushURI, pushHandler(cfg))
|
||||
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
|
||||
}
|
||||
21
router/server_lambda.go
Normal file
21
router/server_lambda.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// +build lambda
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/apex/gateway"
|
||||
)
|
||||
|
||||
// RunHTTPServer provide run http or https protocol.
|
||||
func RunHTTPServer(ctx context.Context, cfg config.ConfYaml, s ...*http.Server) (err error) {
|
||||
if !cfg.Core.Enabled {
|
||||
LogAccess.Debug("httpd server is disabled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
LogAccess.Info("HTTPD server is running on " + cfg.Core.Port + " port.")
|
||||
|
||||
return gateway.ListenAndServe(cfg.Core.Address+":"+cfg.Core.Port, routerEngine())
|
||||
}
|
||||
127
router/server_normal.go
Normal file
127
router/server_normal.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// +build !lambda
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/appleboy/gorush/config"
|
||||
"github.com/appleboy/gorush/logx"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// RunHTTPServer provide run http or https protocol.
|
||||
func RunHTTPServer(ctx context.Context, cfg config.ConfYaml, 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),
|
||||
}
|
||||
} 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), 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 {
|
||||
select {
|
||||
case <-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 {
|
||||
select {
|
||||
case <-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)
|
||||
}
|
||||
445
router/server_test.go
Normal file
445
router/server_test.go
Normal file
@@ -0,0 +1,445 @@
|
||||
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/gorush"
|
||||
"github.com/appleboy/gorush/logx"
|
||||
"github.com/appleboy/gorush/status"
|
||||
|
||||
"github.com/appleboy/gofight/v2"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var goVersion = runtime.Version()
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := initTest()
|
||||
if err := logx.InitLog(
|
||||
cfg.Log.AccessLevel,
|
||||
cfg.Log.AccessLog,
|
||||
cfg.Log.ErrorLevel,
|
||||
cfg.Log.ErrorLog,
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := status.InitAppStatus(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
m.Run()
|
||||
}
|
||||
|
||||
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))
|
||||
}()
|
||||
|
||||
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))
|
||||
}()
|
||||
|
||||
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) {
|
||||
cert := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrekNDQWVPZ0F3SUJBZ0lKQUxiWkVEdlVRckZLTUEwR0NTcUdTSWIzRFFFQkJRVUFNQlF4RWpBUUJnTlYKQkFNTUNXeHZZMkZzYUc5emREQWVGdzB4TmpBek1qZ3dNek13TkRGYUZ3MHlOakF6TWpZd016TXdOREZhTUJReApFakFRQmdOVkJBTU1DV3h2WTJGc2FHOXpkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DCmdnRUJBTWoxK3hnNGpWTHpWbkI1ajduMXVsMzBXRUU0QkN6Y05GeGc1QU9CNUg1cSt3amUwWVlpVkZnNlBReXYKR0NpcHFJUlhWUmRWUTFoSFNldW5ZR0tlOGxxM1NiMVg4UFVKMTJ2OXVSYnBTOURLMU93cWs4cnNQRHU2c1ZUTApxS0tnSDFaOHlhenphUzBBYlh1QTVlOWdPL1J6aWpibnBFUCtxdU00ZHVlaU1QVkVKeUxxK0VvSVFZK01NOE1QCjhkWnpMNFhabDd3TDRVc0NON3JQY082VzN0bG5UMGlPM2g5Yy9ZbTJoRmh6K0tOSjlLUlJDdnRQR1pFU2lndEsKYkhzWEgwOTlXRG84di9XcDUvZXZCdy8rSkQwb3B4bUNmSElCQUxIdDl2NTNSdnZzRFoxdDMzUnB1NUM4em5FWQpZMkF5N05neGhxanFvV0pxQTQ4bEplQTBjbHNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkMwYlRVMVhvZmVoCk5LSWVsYXNoSXNxS2lkRFlNQjhHQTFVZEl3UVlNQmFBRkMwYlRVMVhvZmVoTktJZWxhc2hJc3FLaWREWU1Bd0cKQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFBaUpMOElNVHdOWDlYcVFXWURGZ2tHNApBbnJWd1FocmVBcUM5clN4RENqcXFuTUhQSEd6Y0NlRE1MQU1vaDBrT3kyMG5vd1VHTnRDWjB1QnZuWDJxMWJOCmcxanQrR0JjTEpEUjNMTDRDcE5PbG0zWWhPeWN1TmZXTXhUQTdCWGttblNyWkQvN0toQXJzQkVZOGF1bHh3S0oKSFJnTmxJd2Uxb0ZEMVlkWDFCUzVwcDR0MjVCNlZxNEEzRk1NVWtWb1dFNjg4bkUxNjhodlFnd2pySGtnSGh3ZQplTjhsR0UyRGhGcmFYbldtRE1kd2FIRDNIUkZHaHlwcElGTitmN0JxYldYOWdNK1QyWVJUZk9iSVhMV2JxSkxECjNNay9Oa3hxVmNnNGVZNTR3SjF1ZkNVR0FZQUlhWTZmUXFpTlV6OG5od0szdDQ1TkJWVDl5L3VKWHFuVEx5WT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=`
|
||||
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))
|
||||
}()
|
||||
|
||||
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))
|
||||
}()
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
assert.Error(t, RunHTTPServer(context.Background(), cfg))
|
||||
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), 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) {
|
||||
cfg := initTest()
|
||||
|
||||
r := gofight.New()
|
||||
|
||||
r.GET("/api/stat/go").
|
||||
Run(routerEngine(cfg), 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), 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), 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), 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) {
|
||||
cfg := initTest()
|
||||
|
||||
r := gofight.New()
|
||||
|
||||
// notifications is empty.
|
||||
r.POST("/api/push").
|
||||
SetJSON(gofight.D{
|
||||
"notifications": []gorush.PushNotification{},
|
||||
}).
|
||||
Run(routerEngine(cfg), 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",
|
||||
"mutable_content": 1,
|
||||
"topic": "test",
|
||||
"badge": 1,
|
||||
"alert": gofight.D{
|
||||
"title": "title",
|
||||
"body": "body",
|
||||
},
|
||||
},
|
||||
},
|
||||
}).
|
||||
Run(routerEngine(cfg), 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) {
|
||||
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",
|
||||
},
|
||||
{
|
||||
"tokens": []string{"aaaaa", "bbbbb"},
|
||||
"platform": core.PlatFormAndroid,
|
||||
"message": "Welcome",
|
||||
},
|
||||
},
|
||||
}).
|
||||
Run(routerEngine(cfg), 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",
|
||||
},
|
||||
},
|
||||
}).
|
||||
Run(routerEngine(cfg), 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), 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), 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), 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), 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), 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)
|
||||
cfg.Core.Enabled = true
|
||||
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
38
router/version.go
Normal file
38
router/version.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var version string
|
||||
|
||||
// SetVersion for setup version string.
|
||||
func SetVersion(ver string) {
|
||||
version = ver
|
||||
}
|
||||
|
||||
// GetVersion for get current version.
|
||||
func GetVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
// PrintGoRushVersion provide print server engine
|
||||
func PrintGoRushVersion() {
|
||||
fmt.Printf(`GoRush %s, Compiler: %s %s, Copyright (C) 2019 Bo-Yi Wu, Inc.`,
|
||||
version,
|
||||
runtime.Compiler,
|
||||
runtime.Version())
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// VersionMiddleware : add version on header.
|
||||
func VersionMiddleware() gin.HandlerFunc {
|
||||
// Set out header value for each response
|
||||
return func(c *gin.Context) {
|
||||
c.Header("X-GORUSH-VERSION", version)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user