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,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
}