diff --git a/README.md b/README.md index dd0b333..c53df8c 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ A push notification micro server using [Gin](https://github.com/gin-gonic/gin) f - Support store app stat to memory, [Redis](http://redis.io/), [BoltDB](https://github.com/boltdb/bolt), [BuntDB](https://github.com/tidwall/buntdb), [LevelDB](https://github.com/syndtr/goleveldb) or [BadgerDB](https://github.com/dgraph-io/badger). - Support `p8`, `p12` or `pem` format of iOS certificate file. - Support `/sys/stats` show response time, status code count, etc. -- Support for HTTP proxy to Google server (FCM). +- Support for HTTP proxy. - Support retry send notification if server response is fail. - Support expose [prometheus](https://prometheus.io/) metrics. - Support install TLS certificates from [Let's Encrypt](https://letsencrypt.org/) automatically. @@ -103,7 +103,7 @@ core: key_path: "key.pem" cert_base64: "" key_base64: "" - http_proxy: "" # only working for FCM server + http_proxy: "" pid: enabled: false path: "gorush.pid" @@ -254,7 +254,7 @@ Server Options: -t, --token Notification token -e, --engine Storage engine (memory, redis ...) --title Notification title - --proxy <proxy> Proxy URL (only for GCM) + --proxy <proxy> Proxy URL --pid <pid path> Process identifier path --redis-addr <redis addr> Redis addr (default: localhost:6379) iOS Options: @@ -292,7 +292,7 @@ gorush --android --topic "/topics/foo-bar" \ - `-t`: Device token. - `--title`: Notification title. - `--topic`: Send messages to topics. note: don't add device token. -- `--proxy`: Set http proxy url. (only working for FCM) +- `--proxy`: Set http proxy url. ### Send iOS notification diff --git a/config/config.go b/config/config.go index cb61d6b..13c2b4e 100644 --- a/config/config.go +++ b/config/config.go @@ -26,7 +26,7 @@ core: key_path: "key.pem" cert_base64: "" key_base64: "" - http_proxy: "" # only working for FCM server + http_proxy: "" pid: enabled: false path: "gorush.pid" diff --git a/config/testdata/config.yml b/config/testdata/config.yml index 921b56e..6bd7282 100644 --- a/config/testdata/config.yml +++ b/config/testdata/config.yml @@ -13,7 +13,7 @@ core: key_path: "key.pem" cert_base64: "" key_base64: "" - http_proxy: "" # only working for FCM server + http_proxy: "" pid: enabled: false path: "gorush.pid" diff --git a/gorush/notification_apns.go b/gorush/notification_apns.go index dc4b289..ce13920 100644 --- a/gorush/notification_apns.go +++ b/gorush/notification_apns.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/base64" "errors" + "net/http" "path/filepath" "time" @@ -14,8 +15,11 @@ import ( "github.com/sideshow/apns2/payload" "github.com/sideshow/apns2/token" "github.com/sirupsen/logrus" + "golang.org/x/net/http2" ) +var idleConnTimeout = 90 * time.Second + // Sound sets the aps sound on the payload. type Sound struct { Critical int `json:"critical,omitempty"` @@ -84,23 +88,87 @@ func InitAPNSClient() error { // TeamID from developer account (View Account -> Membership) TeamID: PushConf.Ios.TeamID, } - if PushConf.Ios.Production { - ApnsClient = apns2.NewTokenClient(token).Production() - } else { - ApnsClient = apns2.NewTokenClient(token).Development() - } + + ApnsClient, err = newApnsTokenClient(token) } else { - if PushConf.Ios.Production { - ApnsClient = apns2.NewClient(certificateKey).Production() - } else { - ApnsClient = apns2.NewClient(certificateKey).Development() - } + ApnsClient, err = newApnsClient(certificateKey) + } + + if err != nil { + LogError.Error("Transport Error:", err.Error()) + + return err } } return nil } +func newApnsClient(certificate tls.Certificate) (*apns2.Client, error) { + var client *apns2.Client + + if PushConf.Ios.Production { + client = apns2.NewClient(certificate).Production() + } else { + client = apns2.NewClient(certificate).Development() + } + + if PushConf.Core.HTTPProxy == "" { + return client, nil + } + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{certificate}, + } + + if len(certificate.Certificate) > 0 { + tlsConfig.BuildNameToCertificate() + } + + transport := &http.Transport{ + TLSClientConfig: tlsConfig, + Proxy: http.DefaultTransport.(*http.Transport).Proxy, + IdleConnTimeout: idleConnTimeout, + } + + transportErr := http2.ConfigureTransport(transport) + if transportErr != nil { + return nil, transportErr + } + + client.HTTPClient.Transport = transport + + return client, nil +} + +func newApnsTokenClient(token *token.Token) (*apns2.Client, error) { + var client *apns2.Client + + if PushConf.Ios.Production { + client = apns2.NewTokenClient(token).Production() + } else { + client = apns2.NewTokenClient(token).Development() + } + + if PushConf.Core.HTTPProxy == "" { + return client, nil + } + + transport := &http.Transport{ + Proxy: http.DefaultTransport.(*http.Transport).Proxy, + IdleConnTimeout: idleConnTimeout, + } + + transportErr := http2.ConfigureTransport(transport) + if transportErr != nil { + return nil, transportErr + } + + client.HTTPClient.Transport = transport + + return client, nil +} + func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload.Payload { // Alert dictionary diff --git a/gorush/notification_apns_test.go b/gorush/notification_apns_test.go index 0e4631c..582c0c4 100644 --- a/gorush/notification_apns_test.go +++ b/gorush/notification_apns_test.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "log" + "net/http" + "net/url" "os" "testing" "time" @@ -638,6 +640,42 @@ func TestAPNSClientVaildToken(t *testing.T) { assert.Equal(t, apns2.HostProduction, ApnsClient.Host) } +func TestAPNSClientUseProxy(t *testing.T) { + PushConf, _ = 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() + assert.Nil(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + + req, _ := http.NewRequest("GET", apns2.HostDevelopment, nil) + actualProxyURL, err := ApnsClient.HTTPClient.Transport.(*http.Transport).Proxy(req) + assert.Nil(t, err) + + expectedProxyURL, _ := url.ParseRequestURI(PushConf.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() + assert.Nil(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + assert.NotNil(t, ApnsClient.Token) + + req, _ = http.NewRequest("GET", apns2.HostDevelopment, nil) + actualProxyURL, err = ApnsClient.HTTPClient.Transport.(*http.Transport).Proxy(req) + assert.Nil(t, err) + + expectedProxyURL, _ = url.ParseRequestURI(PushConf.Core.HTTPProxy) + assert.Equal(t, expectedProxyURL, actualProxyURL) + + http.DefaultTransport.(*http.Transport).Proxy = nil +} + func TestPushToIOS(t *testing.T) { PushConf, _ = config.LoadConf("") diff --git a/main.go b/main.go index 93270b7..15f1582 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,6 @@ func main() { topic string message string token string - proxy string title string ) @@ -57,7 +56,7 @@ func main() { flag.BoolVar(&opts.Ios.Enabled, "ios", false, "send ios notification") flag.BoolVar(&opts.Ios.Production, "production", false, "production mode in iOS") flag.StringVar(&topic, "topic", "", "apns topic in iOS") - flag.StringVar(&proxy, "proxy", "", "http proxy url") + flag.StringVar(&opts.Core.HTTPProxy, "proxy", "", "http proxy url") flag.BoolVar(&ping, "ping", false, "ping server") flag.Usage = usage @@ -113,14 +112,11 @@ func main() { log.Fatalf("Can't load log module, error: %v", err) } - // set http proxy for GCM - if proxy != "" { - err = gorush.SetProxy(proxy) + if opts.Core.HTTPProxy != "" { + gorush.PushConf.Core.HTTPProxy = opts.Core.HTTPProxy + } - if err != nil { - gorush.LogError.Fatalf("Set Proxy error: %v", err) - } - } else if gorush.PushConf.Core.HTTPProxy != "" { + if gorush.PushConf.Core.HTTPProxy != "" { err = gorush.SetProxy(gorush.PushConf.Core.HTTPProxy) if err != nil { @@ -268,7 +264,7 @@ Server Options: -t, --token <token> Notification token -e, --engine <engine> Storage engine (memory, redis ...) --title <title> Notification title - --proxy <proxy> Proxy URL (only for GCM) + --proxy <proxy> Proxy URL --pid <pid path> Process identifier path --redis-addr <redis addr> Redis addr (default: localhost:6379) --ping healthy check command for container