upgrade apns2 to 0.13 (#297)
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
56
vendor/github.com/sideshow/apns2/README.md
generated
vendored
56
vendor/github.com/sideshow/apns2/README.md
generated
vendored
@@ -1,5 +1,7 @@
|
||||
# APNS/2
|
||||
|
||||
NOTE: This is an experimental branch for the purpose of testing the new token based authentication
|
||||
|
||||
APNS/2 is a go package designed for simple, flexible and fast Apple Push Notifications on iOS, OSX and Safari using the new HTTP/2 Push provider API.
|
||||
|
||||
[](https://travis-ci.org/sideshow/apns2) [](https://coveralls.io/github/sideshow/apns2?branch=master) [](https://godoc.org/github.com/sideshow/apns2)
|
||||
@@ -7,29 +9,29 @@ APNS/2 is a go package designed for simple, flexible and fast Apple Push Notific
|
||||
## Features
|
||||
|
||||
- Uses new Apple APNs HTTP/2 connection
|
||||
- Fast - See [notes on speed](https://github.com/sideshow/apns2/wiki/APNS-HTTP-2-Push-Speed)
|
||||
- Works with go 1.6 and later
|
||||
- Supports new Apple Token Based Authentication (JWT)
|
||||
- Supports new iOS 10 features such as Collapse IDs, Subtitles and Mutable Notifications
|
||||
- Supports persistent connections to APNs
|
||||
- Supports VoIP/PushKit notifications (iOS 8 and later)
|
||||
- Fast, modular & easy to use
|
||||
- Modular & easy to use
|
||||
- Tested and working in APNs production environment
|
||||
|
||||
## Install
|
||||
|
||||
- Make sure you have [Go](https://golang.org/doc/install) installed and have set your [GOPATH](https://golang.org/doc/code.html#GOPATH).
|
||||
- Download and install the dependencies:
|
||||
|
||||
```sh
|
||||
go get -u golang.org/x/net/http2
|
||||
go get -u golang.org/x/crypto/pkcs12
|
||||
```
|
||||
|
||||
- Install apns2:
|
||||
|
||||
```sh
|
||||
go get -u github.com/sideshow/apns2
|
||||
```
|
||||
|
||||
If you are running the test suite you will also need to install testify:
|
||||
```sh
|
||||
go get -u github.com/stretchr/testify
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
@@ -66,6 +68,34 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## JWT Token Example
|
||||
|
||||
Instead of using a `.p12` or `.pem` certificate as above, you can optionally use
|
||||
APNs JWT _Provider Authentication Tokens_. First you will need a signing key (`.p8` file), Key ID and Team ID [from Apple](http://help.apple.com/xcode/mac/current/#/dev54d690a66). Once you have these details, you can create a new client:
|
||||
|
||||
```go
|
||||
authKey, err := token.AuthKeyFromFile("../AuthKey_XXX.p8")
|
||||
if err != nil {
|
||||
log.Fatal("token error:", err)
|
||||
}
|
||||
|
||||
token := &token.Token{
|
||||
AuthKey: authKey,
|
||||
// KeyID from developer account (Certificates, Identifiers & Profiles -> Keys)
|
||||
KeyID: "ABC123DEFG",
|
||||
// TeamID from developer account (View Account -> Membership)
|
||||
TeamID: "DEF123GHIJ",
|
||||
}
|
||||
...
|
||||
|
||||
client := apns2.NewTokenClient(token)
|
||||
res, err := client.Push(notification)
|
||||
```
|
||||
|
||||
- You can use one APNs signing key to authenticate tokens for multiple apps.
|
||||
- A signing key works for both the development and production environments.
|
||||
- A signing key doesn’t expire but can be revoked.
|
||||
|
||||
## Notification
|
||||
|
||||
At a minimum, a _Notification_ needs a _DeviceToken_, a _Topic_ and a _Payload_.
|
||||
@@ -138,6 +168,16 @@ res, err := client.PushWithContext(ctx, notification)
|
||||
defer cancel()
|
||||
```
|
||||
|
||||
## Speed & Performance
|
||||
|
||||
Also see the wiki page on [APNS HTTP 2 Push Speed](https://github.com/sideshow/apns2/wiki/APNS-HTTP-2-Push-Speed).
|
||||
|
||||
For best performance, you should hold on to an `apns2.Client` instance and not re-create it every push. The underlying TLS connection itself can take a few seconds to connect and negotiate, so if you are setting up an `apns2.Client` and tearing it down every push, then this will greatly affect performance. (Apple suggest keeping the connection open all the time).
|
||||
|
||||
You should also limit the amount of `apns2.Client` instances. The underlying transport has a http connection pool itself, so a single client instance will be enough for most users (One instance can potentially do 4,000+ pushes per second). If you need more than this then one instance per CPU core is a good starting point.
|
||||
|
||||
Speed is greatly affected by the location of your server and the quality of your network connection. If you're just testing locally, behind a proxy or if your server is outside USA then you're not going to get great performance. With a good server located in AWS, you should be able to get [decent throughput](https://github.com/sideshow/apns2/wiki/APNS-HTTP-2-Push-Speed).
|
||||
|
||||
## Command line tool
|
||||
|
||||
APNS/2 has a command line tool that can be installed with `go get github.com/sideshow/apns2/apns2`. Usage:
|
||||
|
||||
66
vendor/github.com/sideshow/apns2/client.go
generated
vendored
66
vendor/github.com/sideshow/apns2/client.go
generated
vendored
@@ -13,6 +13,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sideshow/apns2/token"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
@@ -33,13 +34,31 @@ var (
|
||||
// HTTPClient. The timeout includes connection time, any redirects,
|
||||
// and reading the response body.
|
||||
HTTPClientTimeout = 60 * time.Second
|
||||
// TCPKeepAlive specifies the keep-alive period for an active network
|
||||
// connection. If zero, keep-alives are not enabled.
|
||||
TCPKeepAlive = 60 * time.Second
|
||||
)
|
||||
|
||||
// DialTLS is the default dial function for creating TLS connections for
|
||||
// non-proxied HTTPS requests.
|
||||
var DialTLS = func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: TLSDialTimeout,
|
||||
KeepAlive: TCPKeepAlive,
|
||||
}
|
||||
return tls.DialWithDialer(dialer, network, addr, cfg)
|
||||
}
|
||||
|
||||
// Client represents a connection with the APNs
|
||||
type Client struct {
|
||||
HTTPClient *http.Client
|
||||
Certificate tls.Certificate
|
||||
Host string
|
||||
Certificate tls.Certificate
|
||||
Token *token.Token
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type connectionCloser interface {
|
||||
CloseIdleConnections()
|
||||
}
|
||||
|
||||
// NewClient returns a new Client with an underlying http.Client configured with
|
||||
@@ -62,9 +81,7 @@ func NewClient(certificate tls.Certificate) *Client {
|
||||
}
|
||||
transport := &http2.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
return tls.DialWithDialer(&net.Dialer{Timeout: TLSDialTimeout}, network, addr, cfg)
|
||||
},
|
||||
DialTLS: DialTLS,
|
||||
}
|
||||
return &Client{
|
||||
HTTPClient: &http.Client{
|
||||
@@ -76,6 +93,28 @@ func NewClient(certificate tls.Certificate) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
// NewTokenClient 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; don’t repeatedly open and close connections. APNs treats rapid
|
||||
// connection and disconnection as a denial-of-service attack.
|
||||
func NewTokenClient(token *token.Token) *Client {
|
||||
transport := &http2.Transport{
|
||||
DialTLS: DialTLS,
|
||||
}
|
||||
return &Client{
|
||||
Token: token,
|
||||
HTTPClient: &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: HTTPClientTimeout,
|
||||
},
|
||||
Host: DefaultHost,
|
||||
}
|
||||
}
|
||||
|
||||
// Development sets the Client to use the APNs development push endpoint.
|
||||
func (c *Client) Development() *Client {
|
||||
c.Host = HostDevelopment
|
||||
@@ -116,6 +155,11 @@ func (c *Client) PushWithContext(ctx Context, n *Notification) (*Response, error
|
||||
|
||||
url := fmt.Sprintf("%v/3/device/%v", c.Host, n.DeviceToken)
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
|
||||
|
||||
if c.Token != nil {
|
||||
c.setTokenHeader(req)
|
||||
}
|
||||
|
||||
setHeaders(req, n)
|
||||
|
||||
httpRes, err := c.requestWithContext(ctx, req)
|
||||
@@ -135,6 +179,18 @@ func (c *Client) PushWithContext(ctx Context, n *Notification) (*Response, error
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// CloseIdleConnections closes any underlying connections which were previously
|
||||
// connected from previous requests but are now sitting idle. It will not
|
||||
// interrupt any connections currently in use.
|
||||
func (c *Client) CloseIdleConnections() {
|
||||
c.HTTPClient.Transport.(connectionCloser).CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (c *Client) setTokenHeader(r *http.Request) {
|
||||
c.Token.GenerateIfExpired()
|
||||
r.Header.Set("authorization", fmt.Sprintf("bearer %v", c.Token.Bearer))
|
||||
}
|
||||
|
||||
func setHeaders(r *http.Request, n *Notification) {
|
||||
r.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
if n.Topic != "" {
|
||||
|
||||
108
vendor/github.com/sideshow/apns2/token/token.go
generated
vendored
Normal file
108
vendor/github.com/sideshow/apns2/token/token.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
const (
|
||||
// TokenTimeout is the period of time in seconds that a token is valid for.
|
||||
// If the timestamp for token issue is not within the last hour, APNs
|
||||
// rejects subsequent push messages. This is set to under an hour so that
|
||||
// we generate a new token before the existing one expires.
|
||||
TokenTimeout = 3000
|
||||
)
|
||||
|
||||
// Possible errors when parsing a .p8 file.
|
||||
var (
|
||||
ErrAuthKeyNotPem = errors.New("token: AuthKey must be a valid .p8 PEM file")
|
||||
ErrAuthKeyNotECDSA = errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
|
||||
ErrAuthKeyNil = errors.New("token: AuthKey was nil")
|
||||
)
|
||||
|
||||
// Token represents an Apple Provider Authentication Token (JSON Web Token).
|
||||
type Token struct {
|
||||
sync.Mutex
|
||||
AuthKey *ecdsa.PrivateKey
|
||||
KeyID string
|
||||
TeamID string
|
||||
IssuedAt int64
|
||||
Bearer string
|
||||
}
|
||||
|
||||
// AuthKeyFromFile loads a .p8 certificate from a local file and returns a
|
||||
// *ecdsa.PrivateKey.
|
||||
func AuthKeyFromFile(filename string) (*ecdsa.PrivateKey, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AuthKeyFromBytes(bytes)
|
||||
}
|
||||
|
||||
// AuthKeyFromBytes loads a .p8 certificate from an in memory byte array and
|
||||
// returns an *ecdsa.PrivateKey.
|
||||
func AuthKeyFromBytes(bytes []byte) (*ecdsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(bytes)
|
||||
if block == nil {
|
||||
return nil, ErrAuthKeyNotPem
|
||||
}
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch pk := key.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
return pk, nil
|
||||
default:
|
||||
return nil, ErrAuthKeyNotECDSA
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateIfExpired checks to see if the token is about to expire and
|
||||
// generates a new token.
|
||||
func (t *Token) GenerateIfExpired() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.Expired() {
|
||||
t.Generate()
|
||||
}
|
||||
}
|
||||
|
||||
// Expired checks to see if the token has expired.
|
||||
func (t *Token) Expired() bool {
|
||||
return time.Now().Unix() >= (t.IssuedAt + TokenTimeout)
|
||||
}
|
||||
|
||||
// Generate creates a new token.
|
||||
func (t *Token) Generate() (bool, error) {
|
||||
if t.AuthKey == nil {
|
||||
return false, ErrAuthKeyNil
|
||||
}
|
||||
issuedAt := time.Now().Unix()
|
||||
jwtToken := &jwt.Token{
|
||||
Header: map[string]interface{}{
|
||||
"alg": "ES256",
|
||||
"kid": t.KeyID,
|
||||
},
|
||||
Claims: jwt.MapClaims{
|
||||
"iss": t.TeamID,
|
||||
"iat": issuedAt,
|
||||
},
|
||||
Method: jwt.SigningMethodES256,
|
||||
}
|
||||
bearer, err := jwtToken.SignedString(t.AuthKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
t.IssuedAt = issuedAt
|
||||
t.Bearer = bearer
|
||||
return true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user