Giter Club home page Giter Club logo

apns2's Introduction

APNS/2

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.

Build Status Coverage Status GoDoc

Features

  • Uses new Apple APNs HTTP/2 connection
  • Fast - See notes on speed
  • Works with go 1.7 and later
  • Supports new Apple Token Based Authentication (JWT)
  • Supports new iOS 10 features such as Collapse IDs, Subtitles and Mutable Notifications
  • Supports new iOS 15 features interruptionLevel and relevanceScore
  • Supports persistent connections to APNs
  • Supports VoIP/PushKit notifications (iOS 8 and later)
  • Modular & easy to use
  • Tested and working in APNs production environment

Install

  • Make sure you have Go installed and have set your GOPATH.
  • Install apns2:
go get -u github.com/sideshow/apns2

If you are running the test suite you will also need to install testify:

go get -u github.com/stretchr/testify

Example

package main

import (
  "log"
  "fmt"

  "github.com/sideshow/apns2"
  "github.com/sideshow/apns2/certificate"
)

func main() {

  cert, err := certificate.FromP12File("../cert.p12", "")
  if err != nil {
    log.Fatal("Cert Error:", err)
  }

  notification := &apns2.Notification{}
  notification.DeviceToken = "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7"
  notification.Topic = "com.sideshow.Apns2"
  notification.Payload = []byte(`{"aps":{"alert":"Hello!"}}`) // See Payload section below

  // If you want to test push notifications for builds running directly from XCode (Development), use
  // client := apns2.NewClient(cert).Development()
  // For apps published to the app store or installed as an ad-hoc distribution use Production()

  client := apns2.NewClient(cert).Production()
  res, err := client.Push(notification)

  if err != nil {
    log.Fatal("Error:", err)
  }

  fmt.Printf("%v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
}

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. Once you have these details, you can create a new client:

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.

notification := &apns2.Notification{
  DeviceToken: "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7",
  Topic: "com.sideshow.Apns2",
  Payload: []byte(`{"aps":{"alert":"Hello!"}}`),
}

You can also set an optional ApnsID, Expiration or Priority.

notification.ApnsID =  "40636A2C-C093-493E-936A-2A4333C06DEA"
notification.Expiration = time.Now()
notification.Priority = apns2.PriorityLow

Payload

You can use raw bytes for the notification.Payload as above, or you can use the payload builder package which makes it easy to construct APNs payloads.

// {"aps":{"alert":"hello","badge":1},"key":"val"}

payload := payload.NewPayload().Alert("hello").Badge(1).Custom("key", "val")

notification.Payload = payload
client.Push(notification)

Refer to the payload docs for more info.

Response, Error handling

APNS/2 draws the distinction between a valid response from Apple indicating whether or not the Notification was sent or not, and an unrecoverable or unexpected Error;

  • An Error is returned if a non-recoverable error occurs, i.e. if there is a problem with the underlying http.Client connection or Certificate, the payload was not sent, or a valid Response was not received.
  • A Response is returned if the payload was successfully sent to Apple and a documented response was received. This struct will contain more information about whether or not the push notification succeeded, its apns-id and if applicable, more information around why it did not succeed.

To check if a Notification was successfully sent;

res, err := client.Push(notification)
if err != nil {
  log.Println("There was an error", err)
  return
}

if res.Sent() {
  log.Println("Sent:", res.ApnsID)
} else {
  fmt.Printf("Not Sent: %v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
}

Context & Timeouts

For better control over request cancellations and timeouts APNS/2 supports contexts. Using a context can be helpful if you want to cancel all pushes when the parent process is cancelled, or need finer grained control over individual push timeouts. See the Google post for more information on contexts.

ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
res, err := client.PushWithContext(ctx, notification)
defer cancel()

Speed & Performance

Also see the wiki page on 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.

Command line tool

APNS/2 has a command line tool that can be installed with go get github.com/sideshow/apns2/apns2. Usage:

apns2 --help
usage: apns2 --certificate-path=CERTIFICATE-PATH --topic=TOPIC [<flags>]

Listens to STDIN to send notifications and writes APNS response code and reason to STDOUT.

The expected format is: <DeviceToken> <APNS Payload>
Example: aff0c63d9eaa63ad161bafee732d5bc2c31f66d552054718ff19ce314371e5d0 {"aps": {"alert": "hi"}}
Flags:
      --help               Show context-sensitive help (also try --help-long and --help-man).
  -c, --certificate-path=CERTIFICATE-PATH
                           Path to certificate file.
  -t, --topic=TOPIC        The topic of the remote notification, which is typically the bundle ID for your app
  -m, --mode="production"  APNS server to send notifications to. `production` or `development`. Defaults to `production`
      --version            Show application version.

License

The MIT License (MIT)

Copyright (c) 2016 Adam Jones

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

apns2's People

Contributors

alissonsales avatar appleboy avatar bouk avatar c3mb0 avatar cassiobotaro avatar catatsuy avatar foobarmeow avatar imhoffd avatar jameshfisher avatar jbendotnet avatar lz1irq avatar menghan avatar moredure avatar neilmorton avatar pshopper avatar shawnps avatar sideshow avatar tommy-muehle avatar unrolled avatar wiggisser avatar xjewer avatar xrisk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apns2's Issues

Can not understand how to generate acceptable pem cert

Hello. Thank you for the lib.

I use this command to convert p12 cer to pem

openssl pkcs12 -in aps.p12 -out aps.pem -nodes -clcerts

Which is works fine when i test it with ruby houston gem.

To test this lib i just use example code from readme, only path to the pem file is changed.

The result is error message

Cert Error:failed to parse PKCS1 private key

Which is referenced to apns2/certificate/certificate.go from this lib.

When it fail i tried to send passphrase as the last param into certificate.FromPemFile without success.

My questions are:

  • What is the correct way to generate p12 for this library?
  • Why same cert works with ruby lib and dose not with current one?

Thanks in advance.


go version go1.6.2 darwin/amd64

When i say works fine i mean push notifications are delivered to destination device.

Loading .p12 certificate: ans1: indefinite length found (not DER)

Hello,

I am trying to use the apns2 module but when I try to load the p12 file I get the following error

ans1: indefinite length found (not DER)

I was able to convert the file to .pem using the command line openssl, and it worked but this is not an ideal solution, I need to be able to use the p12 file directly or convert it to pem at runtime. Sadly the openssl bindings do not offer this functionality as of now. Any suggestions?

apns2 occasionally doesn't send any requests to apple

I have a daemon process that just forwards push notifications to apple using apns2.

A handful of times when the process has started no push notifications were sent to apple. Restarting the apns2 daemon process fixes this. The contents of the apns2 successful and unsuccessful requests are exactly the same.

During the time when delivery of these push notifications failed the devices receive other push notifications. Also during this time the devices are able to directly communicate with the host that the apns2 daemon is running on.

Is there any way to enable detailed trace logging of apns2's http2 communications? For example, was it able to tcp connect, negotiate ssl, or actually send the http request?

Is there any way to enable timeouts of its requests or connection attempts?

When the TLS connection was builded?

I write my push operation according with the demo in ReadMe.md, but i am confused with the follwings:

client := apns2.NewClient(cert)
...
res, err := client.Push(notification)

so, when the connection was builded? when the DialTLS (client.HTTPClient.Transport.(*http2.Transport).DialTLS("tcp", "127.0.0:1", nil)) was called ?

A possible file descriptor leak in ClientManager?

Please ensure that you are using the latest version of the master branch before filing an issue.

Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2 env var.

  1. What version of Go are you using? go version
    1.9.2

  2. What OS and processor architecture are you using? go env
    linux amd64

  3. What did you do?
    Here's my code simulate the situation which reach the MaxAge:

cert, _ := certificate.FromPemFile("cert.pem", "")
manager := apns2.NewClientManager()
manager.MaxAge = time.Second * 5
for {
    resp, err := manager.Get(cert).Production().Push(&apns2.Notification{Topic: apnsTopic, DeviceToken: deviceToken, Payload: payload})
    fmt.Println(resp, err)
    time.Sleep(time.Second * 6)
}
  1. What did you expect to see?
    ClientManager create a new client, and the underlying connections of old client should be closed by GC or call client.CloseIdleConnections().

  2. What did you see instead?
    netstat and lsof shows that number of open connections keep increasing.

Maybe ClientManager should close idle connections on removing old client?
If do this so, what if another goroutine using the same client while we call client.CloseIdleConnections()?

Client.Timeout exceeded while awaiting headers

in my program, i made 1000 goroutines for concurrency send msg to apple; when i check my log, i find a lot of error returns when i call client.Push, the error is "net/http: request canceled (Client.Timeout exceeded while awaiting headers)"; the timeout setting of the client is 10s:client.HTTPClient.Timeout = time.Duration(10) * time.Second;
i have no idea why this happened, is the timeout setting too short?

APNs Token Authentication

Hello there!

Are there plans to add the newly released Token Authentication to the library? If there is work already in progress, when can we expect it to be merged into master and if not, will a PR be welcome?

apns push performance

Hi,when i execute this code(res, err := client.Push(notification)), it cost about 1~3 seconds. but i want push more(probably push 1000 per second). how can i improve the performance. Thanks

how to check health of the connection?

when i create a new client,how can i check health of it ?
In Apple Privider Api Doc, it said: "You can check the health of your connection using an HTTP/2 PING frame." In your "Push" fuction, you said:
"If the underlying http.Client is not currently connected, this method will attempt to reconnect transparently before sending the notification."
so, did i need do something else to keep alive of the connetion?

Install instructions

It would be great if there were detailed install instructions. What is included in the readme is not clear.

What am I supposed to do after running the go get commands?

http2 transport errors

Hi,

Setup as per your readme, substituting notification.DeviceToken and notification.Topic for my own.

I'm getting the following error:

2016/12/11 10:28:39 Error:Post https://api.push.apple.com/3/device/<my_hex_id_goes_here> : http2: Transport: peer server initiated graceful shutdown after some of Request.Body was written; define Request.GetBody to avoid this error

If it makes any difference, this is with go version go1.7.4 linux/amd64

ClientManager for token-based clients?

ClientManager is a great class, it would be nice to have a version of this class (TokenClientManager?) which works for JWT/Auth token based clients. It looks like this could be done without too much effort (make the Get method take a *token.Token, change cacheKey to be based on *token.Token instead of tls.Certificate, make Factory take a *token.Token instead of a tls.Certificate).

certificate.FromPem* cannot parse PKCS#8 format

What did you do?

package main

import "fmt"
import "github.com/sideshow/apns2/certificate"
import "os"

func main() {
  apnsCert, err := certificate.FromPemFile(os.Args[1], "")
  if err != nil {
    fmt.Printf("Unable to read your certificate: %v\n", err)
  } else {
    fmt.Printf("Parsed certificate: %v\n", apnsCert)
  }
}
openssl pkcs12 -in Certificates.p12 -out testcertificate.pem -nodes -clcerts
go run main.go testcertificate.pem

What did you expect to see?

A parsed certificate.

What did you see instead?

$ go run main.go testcertificate.pem
Error parsing private key asn1: structure error: tags don't match (2 vs {class:0 tag:16 length:13 isCompound:true}) {optional:false explicit:false application:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false}  @5
Unable to read your certificate: failed to parse PKCS1 private key

What version of Go are you using?

$ go version
go version go1.9.1 darwin/amd64

Notes

I opened a Stack Overflow question attempting to resolve this. The issue appears to be that sideshow/apns2/certificate assumes the private key to be in PKCS#1 format, whereas openssl pkcs12 generates a file with a private key in PKCS#8 format. I suggest that this lib should be able to parse PKCS#8 format, too.

Status 403 : MissingProviderToken

Hi,

I'm trying apns2 to manage push notification on my iOS app, but i get this response :

Reason: MissingProviderToken
Status: 403

what did I forget?

var (
    Certificate tls.Certificate
)

func InitClient(){
    Certificate, _ = certificate.FromPemFile("./cert.pem", "")
}

func SendNotificationIOS(alert string, deviceToken string) bool {

    notification := &apns.Notification{}
    notification.DeviceToken = deviceToken
    notification.Topic = "BUNDLE_IDENTIFIER"
    notification.Payload = []byte(`{"aps":{"alert":"Hello!"}}`)
    client := apns.NewClient(Certificate).
    res, err := client.Push(notification)

    fmt.Println("APNs ID: ", res.ApnsID)
    fmt.Println("Reason: ", res.Reason)
    fmt.Println("Status: ", res.StatusCode)
    if err != nil {
        return false
    }
    return true
}

Create a new Release

A new release which includes ClientManager would be very welcome :)

Also including the PRs about data races

Apns2 error: dial udp 114.114.114.114:53: i/o timeout

Hi:
when I push apns notice, it return dial udp 114.114.114.114:53: i/o timeout. I think the APNS2 can't connect the Apple's server. How can I fix it?
Below is the error:
[16:45:44 CST 2016/09/06] EROR Error:%!(EXTRA *url.Error=Post https://api.development.push.apple.com/3/device/3f8b4ecbae42914c2aa84300d300f8d2f55f5c7f96ea3770846f0c011c
07a8a5: dial tcp: lookup api.development.push.apple.com on 114.114.114.114:53: dial udp 114.114.114.114:53: i/o timeout)
[16:45:44 CST 2016/09/06] INFO client res: %!(EXTRA *apns2.Response=)
[16:45:46 CST 2016/09/06] EROR push again Error: %!(EXTRA *url.Error=Post https://api.development.push.apple.com/3/device/3f8b4ecbae42914c2aa84300d300f8d2f55f5c7f96ea3770846f0c011c
07a8a5: dial tcp: lookup api.development.push.apple.com on 114.114.114.114:53: dial udp 114.114.114.114:53: i/o timeout)
[16:45:46 CST 2016/09/06] EROR push again fail

Only the first build the push connection has this error.

here is mine code:
func pushApnsAndSaveException(message []byte, client *apns.Client) {
pMsg := pMessage.PushMessage{}
json.Unmarshal(message, &pMsg)

payload := NewPayload()
payload.Alert(pMsg.Content)
payload.Badge(pMsg.Count)
payload.Custom("id", pMsg.Id)
payload.Custom("type", pMsg.Type)
payload.Custom("contentType", pMsg.ContentType)
payload.Sound("sms-received1.caf")

payloadByte, err := json.Marshal(payload)
if err != nil {
    log.Println("json marshal error:", err)
}

log.Println("payload json: ", string(payloadByte))
notification := &apns.Notification{}
notification.DeviceToken = pMsg.Token

notification.Topic = apnsConst.ApnsTopic
notification.Priority = 10
notification.Payload = payloadByte
l4g.Info("payload btye: ", string(payloadByte), ",payload: ", notification.Payload)
res, err := client.Push(notification)
    .........

}

the variable client *apns.Client is global.

Add a better connection pool

The default golang http2 connection pool is good but lacks some important features which would be useful for the apns2 library:

  • There is currently no way to have a minimum amount of TLS connections open (throughput)
  • There is currently no way to have a maximum amount of TLS connections open (avoids exhausing resources). Similarly there is no way to control when connections are opened or closed.
  • Connections are currently not opened until the first request is sent - There is currently no way to connect before the first push is sent.
  • There is no visibility/stats into how many tcp connections and requests are currently active.
  • There is no ability to control pings an keep connections alive from the pool - See issues #19, #45, #51

Ideally we want to write a custom connection pool to address the above issues. It should have reasonable defaults so that no configuration is needed out of the box, but provide the correct settings to customize for those that are using apns2 at scale.

go 1.6

when do apns2 support golang 1.6 ?

Apple voip push support?

Hi,

Is this library support apple voip push? If yes, can you provide us example how to send this kind of notification?

Thanks !

Did anyone test the jwt-token-example ? I got "InvalidProviderToken" error

https://github.com/sideshow/apns2#jwt-token-example

my code

func Push2User() {

	authKey, err := token.AuthKeyFromFile("/root/xxxxx/AuthKey_6737ABXXXX.p8")
	if err != nil {
		log.Fatal("token error:", err)
	}

	token := &token.Token{
		AuthKey: authKey,
		KeyID:   "6737ABXXXX",
		TeamID:  "MNLVE7XXXX",
	}

	notification := &apns2.Notification{}
	notification.DeviceToken = "80F3C8FAF0DC85144D192075D2A884FAACC9492A219C90C6CF6FB7460128A7E0"
	notification.Topic = "com.xxxx.xxxx.io"

	payload := payload.NewPayload().Alert("Hello World").Badge(1).Custom("url", "http://xxxx.co")
	notification.Payload = payload

	log.Println(notification)

	// client := apns2.NewTokenClient(token).Production()

	log.Println(token)
	client := apns2.NewTokenClient(token).Development()
	res, err := client.Push(notification)

	if err != nil {
		log.Fatal("Error:", err)
	}

	fmt.Printf("%v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
}

Can I send multi-request on one apns client without any lock?

I was argueing with my colleague wheather I can write code like that

client = apns.NewClient(...)
for i := array {
go sendpush(client)
}

Can I send push req without lock?
where is the mechanism that make sure the validation of http2 frame on that tcp connection? i am looking for details but haven't got yet

er

Please ensure that you are using the latest version of the master branch before filing an issue.

Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2 env var.

  1. What version of Go are you using? go version
  2. What OS and processor architecture are you using? go env
  3. What did you do?
  4. What did you expect to see?
  5. What did you see instead?

Why I don't get notification when apns2 sends notification successfuly.

hello, i am trying to get notification on my ios app,
apns2 is working fine like this. but my app is not getting notification.

2016/11/27 13:00:12 APNs ID: BA5AED23-31F3-77A6-9E8E-C9C0274ACEE0

of course, I did all things correctly on app side.
I checked my app with other php sample. this is php sample(apns not apns2) url link.
https://www.raywenderlich.com/123862/push-notifications-tutorial

You use
HostDevelopment = "https://api.development.push.apple.com"
HostProduction = "https://api.push.apple.com"

But he uses (php sample)
ssl://gateway.sandbox.push.apple.com:2195

How will I do?
My golang version is 1.7.3
cert.pem is Apple Developer IOS Push Services cert file.
OS: windows 10

it response stautscode=200, means push successful. but my iphone can't received

hi.
when I use it push message to apple's push server, it response stautscode=200, means push successed. but sometings my iphone can't received the push message.
why not 100% received push message by iphone. it's normal or is bug.

it,s my logs.
[2016/09/01 20:47:03 CST] INFO OK APNs ID:%!(EXTRA string=A9D9C902-0565-75D2-8A43-8F8BB92C1297, string=,StatusCode:, int=200, string=,Reason:, string=, string=,Timestamp:, apns2.Time=0001-01-01 00:00:00 +0000 UTC)

How to use proxy server for send apple push notification..

We can send android push notification through proxy using following

transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
client := &http.Client{Transport: transport}
sender := &gcm.Sender{ApiKey: viper.GetString("pushNotification.gcmAPIKey"),Http:client}

but apns2 is using http2 and http2 transport doesn't have any way to set the proxyUrl.

Is it possible to send apple push notification through proxy server?

c.HTTPClient.Do (in c.Push) hanging intermittently

As a preface, I'm reporting this to open a dialog. I think the ultimate problem will be with something I'm doing or a bug deep within the http2 libs of Go.

I've noticed that occasionally the call to c.HTTPClient.Do hangs indefinitely. The problem occurs intermittently and seemingly not because of the certificate used in the connection. Given enough retries (where the connection is remade by making a new handle on apns2.Client), it will succeed without error.

I'm not convinced this is a network issue. It seems something is deadlocking within the http2 libs. I set c.HTTPClient.Timeout to 1 second, which never triggers. Additionally I spin up a timeout goroutine of 3 seconds, which is how I determine that something is hanging, and at which point I attempt a retry. (As a side note, I just realized this may not cleanly kill the connection. Perhaps I should call CloseIdleConnections on the http2 transport?) It doesn't seem to be a network issue. Despite setting GODEBUG=http2debug=2, no http2 logs are outputted.

My sender is massively concurrent, having thousands of goroutines at any moment, but my interpretation is that if it gets to c.HTTPClient.Do, which is documented to be thread-safe, and then hangs, then there is a problem within the http2 libs.

Am I weirdly running out of possible connections or something? Does anyone have thoughts on this?

Client manager has the data race

client manager has the data race on m.initInternals which is not located in the critical section 🔥

How you can find it:

  1. update test
diff --git a/client_manager_test.go b/client_manager_test.go
index 5e5d290..e6e372d 100644
--- a/client_manager_test.go
+++ b/client_manager_test.go
@@ -4,6 +4,7 @@ import (
        "bytes"
        "crypto/tls"
        "reflect"
+       "sync"
        "testing"
        "time"
 
@@ -35,14 +36,22 @@ func TestClientManagerGetWithoutNew(t *testing.T) {
 }
 
 func TestClientManagerAddWithoutNew(t *testing.T) {
+       wg := sync.WaitGroup{}
        manager := apns2.ClientManager{
-               MaxSize: 32,
+               MaxSize: 1,
                MaxAge:  5 * time.Minute,
                Factory: apns2.NewClient,
        }
 
-       manager.Add(apns2.NewClient(mockCert()))
-       assert.Equal(t, 1, manager.Len())
+       for i := 0; i < 2; i++ {
+               wg.Add(1)
+               go func() {
+                       manager.Add(apns2.NewClient(mockCert()))
+                       assert.Equal(t, 1, manager.Len())
+                       wg.Done()
+               }()
+       }
+       wg.Wait()
 }
 
 func TestClientManagerLenWithoutNew(t *testing.T) {
  1. run with race detector
$ go test -race  -v github.com/sideshow/apns2 -run ^TestClientManagerAddWithoutNew$
=== RUN   TestClientManagerAddWithoutNew
==================
WARNING: DATA RACE
Read at 0x00c420077578 by goroutine 8:
  github.com/sideshow/apns2.(*ClientManager).Add()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:63 +0x62
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3

Previous write at 0x00c420077578 by goroutine 7:
  github.com/sideshow/apns2.(*ClientManager).Add()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:64 +0x6eb
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3

Goroutine 8 (running) created at:
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
  testing.tRunner()
      /usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9

Goroutine 7 (running) created at:
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
  testing.tRunner()
      /usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9
==================
==================
WARNING: DATA RACE
Write at 0x00c420077588 by goroutine 8:
  sync/atomic.CompareAndSwapInt32()
      /usr/local/Cellar/go/1.7.3/libexec/src/runtime/race_amd64.s:293 +0xb
  sync.(*Mutex).Lock()
      /usr/local/Cellar/go/1.7.3/libexec/src/sync/mutex.go:46 +0x4d
  github.com/sideshow/apns2.(*ClientManager).Add()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:66 +0x8c
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3

Previous write at 0x00c420077588 by goroutine 7:
  github.com/sideshow/apns2.(*ClientManager).Add()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:64 +0x7f4
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3

Goroutine 8 (running) created at:
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
  testing.tRunner()
      /usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9

Goroutine 7 (finished) created at:
  github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
      /Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
  testing.tRunner()
      /usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9
==================
--- PASS: TestClientManagerAddWithoutNew (0.00s)
PASS
Found 2 data race(s)
exit status 66
FAIL	github.com/sideshow/apns2	1.029s

Thoughts on possible ClientConnPool

Unless I'm misunderstanding something, it would make sense to provide a client pool for APNS connections. It would be helpful for those who use this lib with multiple certificates.

My understanding is the ClientConnPool in the http2 libs cannot be used because the underlying Transport is used across all clients in the pool. That wouldn't work for this lib, as the TLS connection is configured with the Apple certificate for each client's transport.

If this is deemed a good idea and a possibility, I'd be willing to start a PR.

Add more example code and docs

  • Document common APNs gotchas and errors (dev/prod gateway etc)
  • Examples of how to use with go channels and go routines
  • Document Client timeout behaviour (issue #24)
  • Document ClientManager and concurrent access safety (issue #44)
  • Document Generating correct .pem/p12 certificates (issue #33 and issue #46)

url.Error "unexpected EOF"

apns2 has been working great in production, for the most part. However, occasionally, I see inexplicable "unexpected EOF" errors. The error is a &url.Error{Op:"Post", URL:"https://api.push.apple.com/3/device/<omitted>", Err:(*errors.errorString)(0xc82000a150)}, where the Err property of the url error is "unexpected EOF". The error is not a temporary url.Error, so I assumed it may be an issue with my apns2.Client's connection, which I am keeping open. So when I encounter the error, I remake the client (thus remaking the connection), but the error continues to happen (makes sense, it's a permanent error). I'm just not sure what it could be, exactly.

Any ideas for what this means or how I could debug?

Using apns2 a6f9928377a5bf0f0605e3410f2a13f286f7a5a9.

iOS 10: Notification Mutable?

iOS 10 Adds another key you can set on the payload, mutable-content which can be set to 0 or 1 based on if you wish to intercept the payload and mutate or replace it before the user sees it.

Will need to expand apsn2 to include that and make sure it's support via CLI and payload builder.

I'll put in a PR for this, to follow in the next few days.

APNs invalid certificate yields (confusing) connection error

When you try to send data with an invalid certificate, you get this error:

Post https://api.push.apple.com/3/device/_token_goes_here_: http2: Transport: peer server initiated graceful shutdown after some of Request.Body was written; define Request.GetBody to avoid this error

This makes it look like a connection or implementation issue, while it is not.

When looking at the http2 logs, you can see that the gateway is explicitly telling you why they are cutting connection, the certificate is wrong: APNsConnectionCut.txt. The interesting part is: http2: Transport received GOAWAY len=46 LastStreamID=0 ErrCode=NO_ERROR Debug="{"reason":"BadCertificateEnvironment"}"
While it would have been nice from the Gateway to use the usual status error code to tell that the certificate is wrong, it does not. If there is an easy way to read GOAWAY frames to return an error that includes the eventual Debug field from Transport, it would be great to use it. If not, you may archive this report.

  1. Version: go1.7.4
  2. Env: running on darwin/amd64, same issue with a static binary on linux
  3. How-To-Reproduce: posting one silent push notification with an invalid certificate (observed on prod gateway)
  4. Observed: client.Push returns a connection/implementation error.
  5. Expected: client.Push returns a error that includes the Debug field of the associated Transport when available.

Beginner question - how to install and run (Win)

Please ensure that you are using the latest version of the master branch before filing an issue.

Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2 env var.

  1. What version of Go are you using? go version
    go version go1.8.3 windows/amd64

  2. What OS and processor architecture are you using? go env
    windows amd64

  3. What did you do?

$ go get github.com/sideshow/apns2/apns2

$ apns2 --help
bash: apns2: command not found

  1. What did you expect to see?
    apns2 help section

  2. What did you see instead?
    apns2 not found.

First time using go. I expected that go get <> would install package and would add executable to path.


Update

I have found that C:\Users\<myUser>\go\bin contains the apns2.exe. I have added this to the path, and its working

$ apns2
apns2.exe: error: required flag --certificate-path not provided, try --help

Pushkit support?

Hi, I saw in README that this library support Pushkit, but I cannot find the implementation as well as the base url of gateway.push.apple.com for Pushkit send notification. Is there any example or explanation regarding the pushkit implementation?

How do you send notifications to all users?

Please ensure that you are using the latest version of the master branch before filing an issue.

Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2 env var.

  1. What version of Go are you using? go version
  2. What OS and processor architecture are you using? go env
  3. What did you do?
  4. What did you expect to see?
    Send notifications to all users.
  5. What did you see instead?

remote error: error decrypting message

Use '_example / main.go', run returns an error
2016/05/31 15:39:42 Error: Post https://api.development.push.apple.com/3/device/269ccfa053618d819aa4e7150455e04163bf6b0f9d598affc9a596b7d215222f: remote error: error decrypting message

Version go1.6.2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.