Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/push/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"notification.go",
],
importpath = "go-common/app/service/main/push/dao/apns2",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//library/stat:go_default_library",
"//library/stat/prom:go_default_library",
"@org_golang_x_net//http2:go_default_library",
"@org_golang_x_net//proxy:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,263 @@
// Package apns2 is a go Apple Push Notification Service (APNs) provider that
// allows you to send remote notifications to your iOS, tvOS, and OS X
// apps, using the new APNs HTTP/2 network protocol.
package apns2
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strconv"
"time"
"go-common/library/log"
"go-common/library/stat"
"go-common/library/stat/prom"
"golang.org/x/net/http2"
"golang.org/x/net/proxy"
)
const (
// HostDevelopment dev host.
HostDevelopment = "https://api.development.push.apple.com"
// HostProduction pro host.
HostProduction = "https://api.push.apple.com"
// StatusCodeSuccess success.
StatusCodeSuccess = 200
// StatusCodeBadReq bad req.
StatusCodeBadReq = 400
// StatusCodeCerErr There was an error with the certificate.
StatusCodeCerErr = 403
// StatusCodeMethodErr The request used a bad :method value. Only POST requests are supported.
StatusCodeMethodErr = 405
// StatusCodeNotForTopic The device token is not form the topic.
StatusCodeNotForTopic = 400
// StatusCodeNoActive The device token is no longer active for the topic.
StatusCodeNoActive = 410
// StatusCodePayloadTooLarge The notification payload was too large.
StatusCodePayloadTooLarge = 413
// StatusCodeTooManyReq The server received too many requests for the same device token.
StatusCodeTooManyReq = 429
// StatusCodeServerErr Internal server error
StatusCodeServerErr = 500
// StatusCodeServerUnavailable The server is shutting down and unavailable.
StatusCodeServerUnavailable = 503
)
// DefaultHost is a mutable var for testing purposes
var DefaultHost = HostDevelopment
// Client represents a connection with the APNs
type Client struct {
HTTPClient *http.Client
Certificate tls.Certificate
Host string
BoundID string
Stats stat.Stat
}
// func init() {
// proxy.RegisterDialerType("http", func(*url.URL, proxy.Dialer) (proxy.Dialer, error) {
// return &net.Dialer{}, nil
// })
// }
// NewClient returns a new Client with an underlying http.Client configured with
// the correct APNs HTTP/2 transport settings. It does not connect to the APNs
// until the first Notification is sent via the Push method.
//
// As per the Apple APNs Provider API, you should keep a handle on this client
// so that you can keep your connections with APNs open across multiple
// notifications; dont repeatedly open and close connections. APNs treats rapid
// connection and disconnection as a denial-of-service attack.
func NewClient(certificate tls.Certificate, timeout time.Duration) *Client {
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{certificate},
ClientAuth: tls.NoClientCert,
}
if len(certificate.Certificate) > 0 {
tlsConfig.BuildNameToCertificate()
}
transport := &http2.Transport{
TLSClientConfig: tlsConfig,
}
// transport := &http.Transport{
// TLSClientConfig: tlsConfig,
// Proxy: func(_ *http.Request) (*url.URL, error) {
// return url.Parse("http://10.28.10.11:80")
// },
// DialContext: (&net.Dialer{
// Timeout: 30 * time.Second,
// KeepAlive: 30 * time.Second,
// DualStack: true,
// }).DialContext,
// MaxIdleConns: 100,
// IdleConnTimeout: 90 * time.Second,
// TLSHandshakeTimeout: 10 * time.Second,
// ExpectContinueTimeout: 1 * time.Second,
// }
return &Client{
HTTPClient: &http.Client{Transport: transport, Timeout: timeout},
Certificate: certificate,
Host: DefaultHost,
Stats: prom.HTTPClient,
}
}
// NewClientWithProxy returns a new Client with sock5 proxy.
func NewClientWithProxy(certificate tls.Certificate, timeout time.Duration, proxyAddr string) *Client {
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{certificate},
ClientAuth: tls.NoClientCert,
}
if len(certificate.Certificate) > 0 {
tlsConfig.BuildNameToCertificate()
}
return &Client{
HTTPClient: &http.Client{Transport: proxyTransport(proxyAddr, tlsConfig, timeout), Timeout: timeout},
Certificate: certificate,
Host: DefaultHost,
Stats: prom.HTTPClient,
}
}
func proxyTransport(proxyAddr string, config *tls.Config, timeout time.Duration) *http2.Transport {
return &http2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (nc net.Conn, err error) {
dialer := &net.Dialer{Timeout: timeout / 2}
var proxyDialer proxy.Dialer
if proxyDialer, err = proxy.SOCKS5("tcp", proxyAddr, nil, dialer); err != nil {
log.Error("proxy.SOCKS5(%s) error(%v)", proxyAddr, err)
return nil, err
}
// u, _ := url.Parse("http://10.28.10.11:80")
// proxyDialer, err = proxy.FromURL(u, dialer)
var conn net.Conn
if conn, err = proxyDialer.Dial(network, addr); err != nil {
log.Error("proxyDialer.Dial(%s,%s) error(%v)", network, addr, err)
if conn, err = dialer.Dial(network, addr); err != nil {
log.Error("dialer.Dial(%s,%s) error(%v)", network, addr, err)
return nil, err
}
}
tlsConn := tls.Client(conn, cfg)
if err = tlsConn.Handshake(); err != nil {
log.Error("tlsConn.Handshake() error(%v)", err)
return nil, err
}
if !cfg.InsecureSkipVerify {
if err = tlsConn.VerifyHostname(cfg.ServerName); err != nil {
log.Error("tlsConn.VerifyHostname(%s) error(%v)", cfg.ServerName, err)
return nil, err
}
}
state := tlsConn.ConnectionState()
if state.NegotiatedProtocol != http2.NextProtoTLS {
err = fmt.Errorf("http2: unexpected ALPN protocol(%s) expect(%s)", state.NegotiatedProtocol, http2.NextProtoTLS)
return nil, err
}
if !state.NegotiatedProtocolIsMutual {
err = errors.New("http2: could not negotiate protocol mutually")
return nil, err
}
return tlsConn, nil
},
TLSClientConfig: config,
}
}
// Development sets the Client to use the APNs development push endpoint.
func (c *Client) Development() *Client {
c.Host = HostDevelopment
return c
}
// Production sets the Client to use the APNs production push endpoint.
func (c *Client) Production() *Client {
c.Host = HostProduction
return c
}
// Push sends a Notification to the APNs gateway. If the underlying http.Client
// is not currently connected, this method will attempt to reconnect
// transparently before sending the notification.
func (c *Client) Push(deviceToken string, payload *Payload, overTime int64) (response *Response, err error) {
if c.Stats != nil {
now := time.Now()
defer func() {
c.Stats.Timing(c.Host, int64(time.Since(now)/time.Millisecond))
log.Info("apns stats timing: %v", int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(c.Host, "failed")
}
}()
}
var (
req *http.Request
res *http.Response
t = time.NewTimer(c.HTTPClient.Timeout)
errCh = make(chan error, 1)
url = fmt.Sprintf("%v/3/device/%v", c.Host, deviceToken)
)
if req, err = http.NewRequest("POST", url, bytes.NewBuffer(payload.Marshal())); err != nil {
log.Error("http.NewRequest(%s) error(%v)", url, err)
return
}
req.Header.Set("apns-topic", c.BoundID)
req.Header.Set("apns-expiration", strconv.FormatInt(overTime, 10))
req.Header.Set("apns-collapse-id", payload.TaskID)
go func() {
res, err = c.HTTPClient.Do(req)
errCh <- err
}()
select {
case <-t.C:
err = errors.New("http.Do timeout")
return
case err = <-errCh:
if err != nil {
log.Error("c.HTTPClient.Do() error(%v)", err)
return
}
}
defer res.Body.Close()
response = &Response{StatusCode: res.StatusCode, ApnsID: res.Header.Get("apns-id")}
var bs []byte
bs, err = ioutil.ReadAll(res.Body)
if err != nil {
log.Error("ioutil.ReadAll() error(%v)", err)
return
} else if len(bs) == 0 {
return
}
if e := json.Unmarshal(bs, &response); e != nil {
if e != io.EOF {
log.Error("json decode body(%s) error(%v)", string(bs), e)
}
}
return
}
// MockPush mock push.
func (c *Client) MockPush(deviceToken string, payload *Payload, overTime int64) (response *Response, err error) {
if c.Stats != nil {
now := time.Now()
defer func() {
c.Stats.Timing(c.Host, int64(time.Since(now)/time.Millisecond))
// log.Info("mock apns stats timing: %v", int64(time.Since(now)/time.Millisecond))
if err != nil {
c.Stats.Incr(c.Host, "apple push mock")
}
}()
}
time.Sleep(200 * time.Millisecond)
response = &Response{StatusCode: StatusCodeSuccess}
return
}

View File

@@ -0,0 +1,175 @@
package apns2
import (
"crypto/tls"
"encoding/json"
"fmt"
"testing"
"time"
"go-common/app/service/main/push/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
k = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3ZDI9NUPfMH0/6DgmeCH/Zl8g3pOlLcMY0p6m8lMe9EDF1vZ
w3uR05YzbQCmYCwWZLKDTq/0d3RMRow8AON1wMv9ynyk7Z1Pb9C+ByN4GCWsRHjN
BbEg8jgBYeOoM6PlpDc16D8uPKukRuaq/j1beOn1HD4vnS8KqUzeS6fYbaiPjC7h
7QZOnNE5NwV6TKPMu/+0aNPNgZDeFtNwcXiiRX6OPfbOOnVr0/6WGorYdGMPLxeZ
viqZ37z7rDhup/LaiDedO/tm09lmKUbFOHS+qbSYPXztDHhOwTm36E1k2mD5Lq1S
d5zHRrwzgPTvieFQ+giA9u8pn2wBNDASBDF1pQIDAQABAoIBAQDdNQtdXUa8IQ1R
FraHCuPa7p2gysCfu22TyC1HUh+ZUqEKdjqg78M1AzXOsyJozDuDR7LPId8qUCND
IAlcPbw3w7Jbsjwbu74ufbLrf58MRLiMGCthbmndSsseh2NMQ2snm7Onb0Tjb95w
pyW69VlZDAQasX9qKCg1xTf/QtFTEGbJpkfN7lYYjwwhLFck39SUH254cwmqV8Tj
AQ9Uj6dQhYvWseSGtsbqkyj0/sUwyzfaUWeNjnYUaVQWxAalnLYiUCzRLR0IMwvM
cmscwYWrWlPPd8ND4yGyHXqeQH2fVJqEx/aIDSpujNeOm2MH0RFRNBU53d/tJpj8
XuqE8Yj1AoGBAO5klAY8vScnC3RQ8x3tlJkM3jkD3FmqSJu7erId0T6KgJz0Xmgj
zHYwvq+V4t0Kmk1Tt5Okq3wI41uDZUJCBXrnNWhm0oDHnlw38TiptBrfqIjfyK5y
OmmP49DWHZagF9nY74zM2doCLPnI9G2i2mAcCjCMXKfeJhOhzkgjnGOTAoGBAO3u
Cy5N9a8yjQ3y0B3iNwWQQs94kfwbyuy4cvvBOlkheB0JTEunNfHVVl4zlwFDnrH1
UWg7ySqTM3iCd9OE1bErbavHmGeTC0FYkKw/k4fct7icU1shSuPwkyp3GRYI6qW8
e24k2U32gO/ANu6WZsYKKAcYjMObQc+/LkLaV3TnAoGAT5QJia964Pf6reBb17C4
OwL9p4CvbMsYI8xIn+6uK7dmSX6ViSPyG74X2VsqeOkSKx/4FvQQPn5lDuZkxeJu
G+HUhT5VpKF+LoCKKIUV1ya0BsTVI86Dyzs6LDtdcyuL6q+s/45eZpT1WIiJd5O2
XADgMeaZA3x3r3QC/TfN+7sCgYAUQvpGxjLO6aIjdvMMKHCBE8jsvBrKel9si0SX
ddwPLQ96gYkyxBmO75j8Sq5oWCbShs6Y7sZxzrlKYOntZFmCTe13/HZZE6eYt/8R
/BQHNN+cZAuhLhOfl6QgsKW9P6Mj3Aoy1gZ/YieWwyqqZLp50PGZsRiDq9wN4f0B
inB6LwKBgQDTeiZ7lAHNhZW8VOpD/K/403xQQinJr6vMiWFm/znwcbm/pteWZuwR
omzn56zhYiXkDKKKxFkVIwf80xSz4Rmgl0p2BndsXU8hOVY/NCnAQJ1uZIsNilcP
KsBKVxvOzopDKZP+C0IQcRaw5VN9WvqJ9ijUEwgm2ufQyz1oj7tREg==
-----END RSA PRIVATE KEY-----`)
c = []byte(`-----BEGIN CERTIFICATE-----
MIIGOTCCBSGgAwIBAgIIDMyy2gAN2P4wDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
HhcNMTgwNzA5MTA0NzQ5WhcNMTkwODA4MTA0NzQ5WjCBqjEkMCIGCgmSJomT8ixk
AQEMFHR2LmRhbm1ha3UuYmlsaWFuaW1lMTIwMAYDVQQDDClBcHBsZSBQdXNoIFNl
cnZpY2VzOiB0di5kYW5tYWt1LmJpbGlhbmltZTETMBEGA1UECwwKNzQ2ODQ1R0M5
NjEsMCoGA1UECgwjU2hhbmdoYWkgQmlsaWJpbGkgQW5pbWF0aW9uIENvLixMdGQx
CzAJBgNVBAYTAkNOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ZDI
9NUPfMH0/6DgmeCH/Zl8g3pOlLcMY0p6m8lMe9EDF1vZw3uR05YzbQCmYCwWZLKD
Tq/0d3RMRow8AON1wMv9ynyk7Z1Pb9C+ByN4GCWsRHjNBbEg8jgBYeOoM6PlpDc1
6D8uPKukRuaq/j1beOn1HD4vnS8KqUzeS6fYbaiPjC7h7QZOnNE5NwV6TKPMu/+0
aNPNgZDeFtNwcXiiRX6OPfbOOnVr0/6WGorYdGMPLxeZviqZ37z7rDhup/LaiDed
O/tm09lmKUbFOHS+qbSYPXztDHhOwTm36E1k2mD5Lq1Sd5zHRrwzgPTvieFQ+giA
9u8pn2wBNDASBDF1pQIDAQABo4ICczCCAm8wDAYDVR0TAQH/BAIwADAfBgNVHSME
GDAWgBSIJxcJqbYYYIvs67r2R1nFUlSjtzCCARwGA1UdIASCARMwggEPMIIBCwYJ
KoZIhvdjZAUBMIH9MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMg
Y2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0
aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25z
IG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHBy
YWN0aWNlIHN0YXRlbWVudHMuMDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxl
LmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eTATBgNVHSUEDDAKBggrBgEFBQcDAjAw
BgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmFwcGxlLmNvbS93d2RyY2EuY3Js
MB0GA1UdDgQWBBRezirr8YHmrv5m5/7ZCr3VybbdLjAOBgNVHQ8BAf8EBAMCB4Aw
EAYKKoZIhvdjZAYDAQQCBQAwEAYKKoZIhvdjZAYDAgQCBQAwgYMGCiqGSIb3Y2QG
AwYEdTBzDBR0di5kYW5tYWt1LmJpbGlhbmltZTAFDANhcHAMGXR2LmRhbm1ha3Uu
YmlsaWFuaW1lLnZvaXAwBgwEdm9pcAwhdHYuZGFubWFrdS5iaWxpYW5pbWUuY29t
cGxpY2F0aW9uMA4MDGNvbXBsaWNhdGlvbjANBgkqhkiG9w0BAQsFAAOCAQEAQLGl
rzH6QG5WKmEbYw3741TTer1E2MlCr7JP9rmn3W+IWy+cX2IQv9vaeFZ3pi/1uMkC
kK6JQd7gUXLPcGwldu4m36OOdUfRLWPH7yvvyYazEo6sDKAUzI/cg14Yj/3Z7ig1
nL6pvXzPd0LjreKKDc08wfmV8gbALLWzjkIcanNdijWlMtfwgLWQunr2jZAK4kKN
GpGku6BCZPYzFLidPMfnXIgOarNbM0SFX+3UY2+fWS+oJsNpGvqWUEINFjVhWoZ4
62WxT8BT2sCbMNGqJGM3wTYe6gkT3E52azu1bNc18/+5/V/qCIjsZLWsX4yCywFE
JQR4w5UPQzMbq6Ybeg==
-----END CERTIFICATE-----`)
hdk = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3ZDI9NUPfMH0/6DgmeCH/Zl8g3pOlLcMY0p6m8lMe9EDF1vZ
w3uR05YzbQCmYCwWZLKDTq/0d3RMRow8AON1wMv9ynyk7Z1Pb9C+ByN4GCWsRHjN
BbEg8jgBYeOoM6PlpDc16D8uPKukRuaq/j1beOn1HD4vnS8KqUzeS6fYbaiPjC7h
7QZOnNE5NwV6TKPMu/+0aNPNgZDeFtNwcXiiRX6OPfbOOnVr0/6WGorYdGMPLxeZ
viqZ37z7rDhup/LaiDedO/tm09lmKUbFOHS+qbSYPXztDHhOwTm36E1k2mD5Lq1S
d5zHRrwzgPTvieFQ+giA9u8pn2wBNDASBDF1pQIDAQABAoIBAQDdNQtdXUa8IQ1R
FraHCuPa7p2gysCfu22TyC1HUh+ZUqEKdjqg78M1AzXOsyJozDuDR7LPId8qUCND
IAlcPbw3w7Jbsjwbu74ufbLrf58MRLiMGCthbmndSsseh2NMQ2snm7Onb0Tjb95w
pyW69VlZDAQasX9qKCg1xTf/QtFTEGbJpkfN7lYYjwwhLFck39SUH254cwmqV8Tj
AQ9Uj6dQhYvWseSGtsbqkyj0/sUwyzfaUWeNjnYUaVQWxAalnLYiUCzRLR0IMwvM
cmscwYWrWlPPd8ND4yGyHXqeQH2fVJqEx/aIDSpujNeOm2MH0RFRNBU53d/tJpj8
XuqE8Yj1AoGBAO5klAY8vScnC3RQ8x3tlJkM3jkD3FmqSJu7erId0T6KgJz0Xmgj
zHYwvq+V4t0Kmk1Tt5Okq3wI41uDZUJCBXrnNWhm0oDHnlw38TiptBrfqIjfyK5y
OmmP49DWHZagF9nY74zM2doCLPnI9G2i2mAcCjCMXKfeJhOhzkgjnGOTAoGBAO3u
Cy5N9a8yjQ3y0B3iNwWQQs94kfwbyuy4cvvBOlkheB0JTEunNfHVVl4zlwFDnrH1
UWg7ySqTM3iCd9OE1bErbavHmGeTC0FYkKw/k4fct7icU1shSuPwkyp3GRYI6qW8
e24k2U32gO/ANu6WZsYKKAcYjMObQc+/LkLaV3TnAoGAT5QJia964Pf6reBb17C4
OwL9p4CvbMsYI8xIn+6uK7dmSX6ViSPyG74X2VsqeOkSKx/4FvQQPn5lDuZkxeJu
G+HUhT5VpKF+LoCKKIUV1ya0BsTVI86Dyzs6LDtdcyuL6q+s/45eZpT1WIiJd5O2
XADgMeaZA3x3r3QC/TfN+7sCgYAUQvpGxjLO6aIjdvMMKHCBE8jsvBrKel9si0SX
ddwPLQ96gYkyxBmO75j8Sq5oWCbShs6Y7sZxzrlKYOntZFmCTe13/HZZE6eYt/8R
/BQHNN+cZAuhLhOfl6QgsKW9P6Mj3Aoy1gZ/YieWwyqqZLp50PGZsRiDq9wN4f0B
inB6LwKBgQDTeiZ7lAHNhZW8VOpD/K/403xQQinJr6vMiWFm/znwcbm/pteWZuwR
omzn56zhYiXkDKKKxFkVIwf80xSz4Rmgl0p2BndsXU8hOVY/NCnAQJ1uZIsNilcP
KsBKVxvOzopDKZP+C0IQcRaw5VN9WvqJ9ijUEwgm2ufQyz1oj7tREg==
-----END RSA PRIVATE KEY-----`)
hdc = []byte(`-----BEGIN CERTIFICATE-----
MIIGPjCCBSagAwIBAgIIRY80KDvYYtUwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
HhcNMTgwNzA5MTA0OTUyWhcNMTkwODA4MTA0OTUyWjCBrDElMCMGCgmSJomT8ixk
AQEMFXR2LmRhbm1ha3UuYmlsaWJpbGloZDEzMDEGA1UEAwwqQXBwbGUgUHVzaCBT
ZXJ2aWNlczogdHYuZGFubWFrdS5iaWxpYmlsaWhkMRMwEQYDVQQLDAo3NDY4NDVH
Qzk2MSwwKgYDVQQKDCNTaGFuZ2hhaSBCaWxpYmlsaSBBbmltYXRpb24gQ28uLEx0
ZDELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDd
kMj01Q98wfT/oOCZ4If9mXyDek6UtwxjSnqbyUx70QMXW9nDe5HTljNtAKZgLBZk
soNOr/R3dExGjDwA43XAy/3KfKTtnU9v0L4HI3gYJaxEeM0FsSDyOAFh46gzo+Wk
NzXoPy48q6RG5qr+PVt46fUcPi+dLwqpTN5Lp9htqI+MLuHtBk6c0Tk3BXpMo8y7
/7Ro082BkN4W03BxeKJFfo499s46dWvT/pYaith0Yw8vF5m+KpnfvPusOG6n8tqI
N507+2bT2WYpRsU4dL6ptJg9fO0MeE7BObfoTWTaYPkurVJ3nMdGvDOA9O+J4VD6
CID27ymfbAE0MBIEMXWlAgMBAAGjggJ2MIICcjAMBgNVHRMBAf8EAjAAMB8GA1Ud
IwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHAYDVR0gBIIBEzCCAQ8wggEL
BgkqhkiG92NkBQEwgf0wgcMGCCsGAQUFBwICMIG2DIGzUmVsaWFuY2Ugb24gdGhp
cyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2NlcHRhbmNlIG9m
IHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlv
bnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRpZmljYXRpb24g
cHJhY3RpY2Ugc3RhdGVtZW50cy4wNQYIKwYBBQUHAgEWKWh0dHA6Ly93d3cuYXBw
bGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5MBMGA1UdJQQMMAoGCCsGAQUFBwMC
MDAGA1UdHwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuYXBwbGUuY29tL3d3ZHJjYS5j
cmwwHQYDVR0OBBYEFF7OKuvxgeau/mbn/tkKvdXJtt0uMA4GA1UdDwEB/wQEAwIH
gDAQBgoqhkiG92NkBgMBBAIFADAQBgoqhkiG92NkBgMCBAIFADCBhgYKKoZIhvdj
ZAYDBgR4MHYMFXR2LmRhbm1ha3UuYmlsaWJpbGloZDAFDANhcHAMGnR2LmRhbm1h
a3UuYmlsaWJpbGloZC52b2lwMAYMBHZvaXAMInR2LmRhbm1ha3UuYmlsaWJpbGlo
ZC5jb21wbGljYXRpb24wDgwMY29tcGxpY2F0aW9uMA0GCSqGSIb3DQEBCwUAA4IB
AQAltymx4RoeuW2Grnk4Vb+RneVafET87kT2HpjKTwnWglcFzDM2g3jeEC/MfDtZ
28Y/qMBmz4ThJthNOHgdEyvTqTdZG4739HzLdxB79GsraGhpMIORw8UOetsmogId
FspzWxR/nysIdEo8bj6gbAOmANrQn1zNFrO5/c31GxY+AFRGl6n/aY7ObCstpIca
L6TDWiPJyLH2Ha0qeGgBch97Jk1XVa2m6Syl9a5VtL8jM8SBDpx+krVsxL4YhBot
Ko45/s6H9wqCIx06h28N3EB0VALGSxeFhwlc/uVQRNu0w3DPHTTGHhNOGbzn2QVs
V5Ies2V9gtxFI5xwR+/ERU2z
-----END CERTIFICATE-----`)
)
func TestClient(t *testing.T) {
// unuserd
_ = hdc
_ = hdk
Convey("test apns", t, func() {
cert, err := tls.X509KeyPair(c, k)
if err != nil {
panic(err)
}
apnsClient := NewClient(cert, 10*time.Second).Production()
aps := Aps{
Alert: Alert{
Title: "test",
Body: "test",
},
Badge: 0,
// Sound: "default", 不加sound没有提醒
MutableContent: 1,
}
var token string
// token = "140b5f62b3db93bc6a072645d3825c50efa5693f733690542fffa034252c7495"
scheme := model.Scheme(model.LinkTypeLive, "123", model.PlatformIPhone, 529000)
payload := &Payload{Aps: aps, URL: scheme, TaskID: "3c9e1eaca2afd373_search_1473317045", Token: token, Image: "https://pic.qiantucdn.com/58pic/12/38/18/13758PIC4GV.jpg"}
bs, _ := json.Marshal(payload)
fmt.Printf("payload(%s)", bs)
apnsClient.BoundID = "tv.danmaku.bilianime"
resp, err := apnsClient.Push(token, payload, time.Now().Unix())
So(err, ShouldBeNil)
fmt.Println("StatusCode:", resp.StatusCode, "ApnsID:", resp.ApnsID, "Reason:", resp.Reason)
})
}

View File

@@ -0,0 +1,81 @@
package apns2
import (
"encoding/json"
)
// NOTE these structs and "Table" refer to https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html
// Payload payload.
type Payload struct {
Aps Aps `json:"aps"`
URL string `json:"url"` // bilibili schedule
TaskID string `json:"task_id"`
Token string `json:"tid"`
Image string `json:"image_url,omitempty"`
}
// Marshal marshals payload.
func (p *Payload) Marshal() []byte {
payload, _ := json.Marshal(p)
return payload
}
// Aps Apple Push Service request meta.
type Aps struct {
// If this property is included, the system displays a standard alert or a banner, based on the users setting.
// You can specify a string or a dictionary as the value of alert.
// If you specify a string, it becomes the message text of an alert with two buttons: Close and View.
// If the user taps View, the app launches.
// If you specify a dictionary, refer to Table 5-2 for descriptions of the keys of this dictionary.
// The JSON \U notation is not supported. Put the actual UTF-8 character in the alert text instead.
Alert Alert `json:"alert,omitempty"`
// The number to display as the badge of the app icon.
// If this property is absent, the badge is not changed. To remove the badge, set the value of this property to 0.
Badge int `json:"badge,omitempty"`
// The name of a sound file in the app bundle or in the Library/Sounds folder of the apps data container.
// The sound in this file is played as an alert. If the sound file doesnt exist or default is specified
// as the value, the default alert sound is played. The audio must be in one of the audio data formats
// that are compatible with system sounds; see Preparing Custom Alert Sounds for details.
Sound string `json:"sound,omitempty"`
// Provide this key with a value of 1 to indicate that new content is available.
// Including this key and value means that when your app is launched in the background or resumed,
// application:didReceiveRemoteNotification:fetchCompletionHandler: is called.
ContentAvailable int `json:"content-available,omitempty"`
// Provide this key with a string value that represents the identifier property of the
// UIMutableUserNotificationCategory object you created to define custom actions.
// To learn more about using custom actions, see Registering Your Actionable Notification Types.
Category string `json:"category,omitempty"`
// MutableContent .
MutableContent int `json:"mutable-content,omitempty"`
}
// Alert alert message.
type Alert struct {
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
// could support any more other field
}
// Response reponse message.
type Response struct {
ApnsID string
// Http status. (refer to Table 6-4)
StatusCode int
// The APNs error string indicating the reason for the notification failure (if
// any). The error code is specified as a string. For a list of possible
// values, see the Reason constants above.
// If the notification was accepted, this value will be "".
Reason string
// If the value of StatusCode is 410, this is the last time at which APNs
// confirmed that the device token was no longer valid for the topic.
// Timestamp time.Time
}