Giter VIP home page Giter VIP logo

buford's Introduction

Buford

Apple Push Notification (APN) Provider library for Go 1.6 and HTTP/2. Send remote notifications to iOS, macOS, tvOS and watchOS. Buford can also sign push packages for Safari notifications and Wallet passes.

Please see releases for updates.

GoDoc Build Status MIT codecov

Documentation

Buford uses Apple's new HTTP/2 Notification API that was announced at WWDC 2015 and released on December 17, 2015.

API documentation is available from GoDoc.

Also see Apple's Local and Remote Notification Programming Guide, especially the sections on the JSON payload and the Notification API.

Terminology

APN Apple Push Notification

Provider The Buford library is used to create a provider of push notifications.

Service Apple's push notification service that Buford communicates with.

Client An http.Client provides an HTTP/2 client to communicate with the APN Service.

Notification A payload, device token, and headers.

Device Token An identifier for an application on a given device.

Payload The JSON sent to a device.

Headers HTTP/2 headers are used to set priority and expiration.

Installation

This library requires Go 1.6.3 or better.

go get -u -d github.com/RobotsAndPencils/buford

Buford depends on several packages outside of the standard library, including the http2 package. Its certificate package depends on the pkcs12 and pushpackage depends on pkcs7. They can be retrieved or updated with:

go get -u golang.org/x/net/http2
go get -u golang.org/x/crypto/pkcs12
go get -u github.com/aai/gocrypto/pkcs7

I am still looking for feedback on the API so it may change. Please copy Buford and its dependencies into a vendor/ folder at the root of your project.

Examples

package main

import (
	"encoding/json"
	"fmt"

	"github.com/RobotsAndPencils/buford/certificate"
	"github.com/RobotsAndPencils/buford/payload"
	"github.com/RobotsAndPencils/buford/payload/badge"
	"github.com/RobotsAndPencils/buford/push"
)

// set these variables appropriately
const (
	filename = "/path/to/certificate.p12"
	password = ""
	host = push.Development
	deviceToken = "c2732227a1d8021cfaf781d71fb2f908c61f5861079a00954a5453f1d0281433"
)

func main() {
	// load a certificate and use it to connect to the APN service:
	cert, err := certificate.Load(filename, password)
	exitOnError(err)

	client, err := push.NewClient(cert)
	exitOnError(err)

	service := push.NewService(client, host)

	// construct a payload to send to the device:
	p := payload.APS{
		Alert: payload.Alert{Body: "Hello HTTP/2"},
		Badge: badge.New(42),
	}
	b, err := json.Marshal(p)
	exitOnError(err)

	// push the notification:
	id, err := service.Push(deviceToken, nil, b)
	exitOnError(err)

	fmt.Println("apns-id:", id)
}

See example/push for the complete listing.

Concurrent use

HTTP/2 can send multiple requests over a single connection, but service.Push waits for a response before returning. Instead, you can wrap a Service in a queue to handle responses independently, allowing you to send multiple notifications at once.

var wg sync.WaitGroup
queue := push.NewQueue(service, numWorkers)

// process responses (responses may be received in any order)
go func() {
	for resp := range queue.Responses {
		log.Println(resp)
		// done receiving and processing one response
		wg.Done()
	}
}()

// send the notifications
for i := 0; i < 100; i++ {
	// increment count of notifications sent and queue it
	wg.Add(1)
	queue.Push(deviceToken, nil, b)
}

// wait for all responses to be processed
wg.Wait()
// shutdown the channels and workers for the queue
queue.Close()

It's important to set up a goroutine to handle responses before sending any notifications, otherwise Push will block waiting for room to return a Response.

You can configure the number of workers used to send notifications concurrently, but be aware that a larger number isn't necessarily better, as Apple limits the number of concurrent streams. From the Apple Push Notification documentation:

"The APNs server allows multiple concurrent streams for each connection. The exact number of streams is based on server load, so do not assume a specific number of streams."

See example/concurrent/ for a complete listing.

Headers

You can specify an ID, expiration, priority, and other parameters via the Headers struct.

headers := &push.Headers{
	ID:          "922D9F1F-B82E-B337-EDC9-DB4FC8527676",
	Expiration:  time.Now().Add(time.Hour),
	LowPriority: true,
	Type:        push.Alert,
}

id, err := service.Push(deviceToken, headers, b)

If no ID is specified APNS will generate and return a unique ID. When an expiration is specified, APNS will store and retry sending the notification until that time, otherwise APNS will not store or retry the notification. LowPriority should always be set when sending a ContentAvailable payload.

Custom values

To add custom values to an APS payload, use the Map method as follows:

p := payload.APS{
	Alert: payload.Alert{Body: "Message received from Bob"},
}
pm := p.Map()
pm["acme2"] = []string{"bang", "whiz"}

b, err := json.Marshal(pm)
if err != nil {
	log.Fatal(b)
}

id, err := service.Push(deviceToken, nil, b)

Error responses

Errors from service.Push or queue.Response could be HTTP errors or an error response from Apple. To access the Reason and HTTP Status code, you must convert the error to a push.Error as follows:

if e, ok := err.(*push.Error); ok {
	switch e.Reason {
	case push.ErrBadDeviceToken:
		// handle error
	}
}

Website Push

Before you can send push notifications through Safari and the Notification Center, you must provide a push package, which is a signed zip file containing some JSON and icons.

Use pushpackage to write a zip to a http.ResponseWriter or to a file. It will create the manifest.json and signature files for you.

pkg := pushpackage.New(w)
pkg.EncodeJSON("website.json", website)
pkg.File("icon.iconset/[email protected]", "static/[email protected]")
// other icons... (required)
if err := pkg.Sign(cert, nil); err != nil {
	log.Fatal(err)
}

NOTE: The filenames added to the zip may contain forward slashes but not back slashes or drive letters.

See example/website/ and the Safari Push Notifications documentation.

Wallet (Passbook) Pass

A pass is a signed zip file with a .pkpass extension and a application/vnd.apple.pkpass MIME type. You can use pushpackage to write a .pkpass that contains a pass.json file.

See example/wallet/ and the Wallet Developer Guide.

Related Projects

  • apns2 Alternative HTTP/2 APN provider library (Go)
  • go-apns-server Mock APN server (Go)
  • gorush A push notification server (Go)
  • Push Encryption Web Push for Chrome and Firefox (Go)
  • apns A Go package to interface with the Apple Push Notification Service (Timehop)
  • apns Utilities for Apple Push Notification and Feedback Services
  • micromdm Mobile Device Management server (Go)
  • Lowdown (Ruby)
  • Apnotic (Ruby)
  • Pigeon (Elixir, iOS and Android)
  • APNSwift (Swift)

buford's People

Contributors

appleboy avatar bjdgyc avatar curtisallen avatar djui avatar flexfrank avatar golint-fixer avatar kaunteya avatar kmcrawford avatar lepidopteron avatar macteo avatar nathany avatar timakin 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

buford's Issues

Accessing push.Service concurrently?

The docs aren't entirely clear about the lifespan of a push.Service. Is a new Service supposed to be created for each individual push notification (or batch of notifications), or is it intended to be long-lived and reused? If the latter is true, is it safe to use across different threads?

Here's what I've got working right now:

package main

import "github.com/RobotsAndPencils/buford/push"
...

var service push.Service

// InitAPNS initializes the APNS service for global access.
func InitAPNS() {
    filename := "/path/to/certificate.p12"
    password := "certificate_password"
    cert, key, err := certificate.Load(filename, password)
    if err != nil {
        log.Fatal(err.Error())
    }
    client, err := push.NewClient(certificate.TLS(cert, key))
    if err != nil {
        log.Fatal(err.Error())
    }
    service = push.Service{
        Client: client,
        Host: push.Development}
}

func SendPushNotification(deviceTokens []string, notificationDetails...) {
    ...
    service.PushBytes(token, nil, bytes)
    ...
}

I initialize my global Service once from main(), and then reuse it each time I call SendPushNotification() - usually from different threads. After 48 hours of running and maybe 50 total notifications, everything seems to be working ok. But, I haven't been able to test it under a real load yet.

Am I going about this the right way? Supposing that Service is meant to be long-lived, how can we catch connection errors? And if it's instead meant to created and destroyed with each notification, what suggestions do you have to avoid reading the certificate from disk each time? Thanks for the help.

Slow sending 50000+ push

for i := 0; i < len(js.Tokens); i++ {
tok := js.Tokens[i]
if tok != "" {
id, err := service.Push(token, nil, p)
}
}
Slow sending. What can you think up to speed sending?

Return status code / response.reason?

It would be nice in the case of errors to return the status code and reason from Apple as part of your Error struct so that calling code wouldn't have to check for your error strings explicitly in order to respond to various conditions.

Return type should be Error

in #97 return type should be Error not builtin error.

With return type Error user will have options like getting timestamp of unregistered error

push: need a signal to stop processing responses

What sort of signal should exit this loop? (to clean up the goroutine)

go func() {
    for {
        // Response blocks until a response is available
        log.Println(queue.Response())
    }
}()

Response returns id string, deviceToken string, err error. Maybe a special error, like io.EOF?

unexpected bad device token error

So, I'm currently having trouble getting APNS to recognize what I believe to be valid device tokens. While attempting to resolve this issue I looked at the request being sent and was surprised to see HTTP/1.1 as the protocol. Am I incorrect in believing that I should be seeing HTTP/2.0?

&{Method:POST URL:https://api.development.push.apple.com/3/device/BBFD8AF1C6A685F8FE4294732D2700B587956508318C770328BE167149F180F6 Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Content-Type:[application/json]] Body:{Reader:0xc820573ad0} ContentLength:40 TransferEncoding:[] Close:false Host:api.development.push.apple.com Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr: RequestURI: TLS:<nil> Cancel:<nil>}

close closed channel

Hi, all

i have updatemy net/http2 package to the newest, i can still get this error #35

i find that this error is triggered when the program has been running for a while.
i think this is caused by the broken connection.

port 2197

Note: You can alternatively use port 2197 when communicating with APNs. You might do this, for example, to allow APNs traffic through your firewall but to block other HTTPS traffic.

Support port 2197. Overriding Service.Host will probably just work, but I should test it out.

Tasks for next releases

I'm just going to bucket everything in one issue for now.

  • Review current API and gather feedback
  • Support additional payloads, such as Safari and test custom values (via Map).
  • Return the apns-id header
  • Handle all documented errors returned by Apple
  • Fully review Apple documentation to ensure nothing is missed
  • Additional service tests and header tests. Go 1.6 beta in CI.
  • Improve documentation
  • Require Go 1.6 or report a meaningful error (not malformed HTTP status code "client")
  • Try Buford with the production APN service (currently only used with the development environment)
  • Try Safari push notifications
  • Parse and return timestamp in response
  • Ensure underlying connection is being reused. Do we need to configure anything or does it Just Work™?

Beyond that, I'd like to start adding some helpers for validations, such as checking for a valid device token. I have code for this from the previous API that just needs to be brought over. Some validations are across headers and the payload, such as not allowing ContentAvailable by itself without a LowPriority header. These validations should be opt in, at least in the lower level APIs, so you can choose performance.

It would be nice if the ID header was a UUID type that was sure to be valid. I don't know if it makes sense to pull in a specific UUID library or not? If someone isn't letting Apple generate the ID, I imagine they want something custom.

SSL Certificate decode error (pkcs12: expected exactly two safe bags in the PFX PDU)

Hey,

I seem to have run into a certificate issue as I get the error: pkcs12: expected exactly two safe bags in the PFX PDU. I'm generating the Production Apple Push Notification Service SSL cert as described in Apple's docs and I had simply exported the certificate/private-key pair from my keychain to get a .p12 file and I'm using this p12 file's path as filename in line cert, err := certificate.Load(filename, password) of the package's example. The error is returned by pkcs12 package in the Decode method of cert.go and specifically, by pkcs12.Decode. Would you happen to know why this error occurs or maybe what steps need to be taken to get the correct p12 file?

Notification management in iOS 10

There is a new header apns-collapse-id to update or remove notifications. It is a string identifying the notification.

Swift also has a removeDeliveredNotifications API for UNUserNotificationCenter but it's not yet clear how to remotely remove notifications. It may be as simple as a DELETE action rather than POST, but I'm awaiting further information.

remote error: tls: unknown certificate

Hello there,

Recently I started seeing weird errors for push notifications on a DEV server.
No notification is sent now.

Post https://api.development.push.apple.com/3/device/{TOKEN}: remote error: tls: unknown certificate

Do you know what it could mean ?
Could it be that my certificate is expired ?

  1. What version of Go are you using (go version)?
    1.7
  2. What operating system (GOOS) are you using (go env) and what version?
    linux amd 64

cheers !

Question: NewClient opens a new connection to Apple and workers on a single service opens a new stream on the same connection ?

Currently Apple documentation states that opening multiple connections to Apple servers is the best practice if you have a large number of notifications to send.

From what I understand, when we create a client and a service in buford, we create a new connection to Apple servers. And when we use a queue with multiple workers we are are using multiple HTTP/2 streams on a single connection.

The application I am using buford in has a requirement where a large number of notifications should be delivered very quickly (ie: 100K in 15s), so I think I need to take advantage of multiple connections, but not really sure how to do that here.

New subtitle field in iOS 10

The APS Alert has a new "subtitle" field in iOS 10 via "Introduction to Notifications"

Not yet in the documentation.

queue.Responses not executed for the last response

What did you do? (steps to reproduce or a code sample is helpful)

➜  go go version
go version go1.6.2 darwin/amd64

➜  go go get -u -d github.com/RobotsAndPencils/buford
➜  go
package main

import(
  "encoding/json"
  "log"
  "crypto/tls"

  "github.com/RobotsAndPencils/buford/payload"
  "github.com/RobotsAndPencils/buford/payload/badge"
  "github.com/RobotsAndPencils/buford/push"
)

const (
  certFile = "../promiser-apns.pem"
  keyFile = "../promiser-apns.key"
  host = push.Development
  deviceToken = "afc61ee7a0d29ddbf68c7299017a549880f50bcb9a9b5e1a45c21dbfb064d047"
)

func main() {
  // load a certificate and use it to connect to the APN service:
  if cert, err := tls.LoadX509KeyPair(certFile, keyFile); err == nil {

    if client, err := push.NewClient(cert); err == nil {

      // construct a payload to send to the device:
      payloadObj := payload.APS{
        Alert: payload.Alert{Body: "Hello HTTP/2"},
        Badge: badge.New(42),
      }

      if payloadData, err := json.Marshal(payloadObj); err == nil {

        service := push.NewService(client, host)
        queue := push.NewQueue(service, 5)

        go func() {
          for resp := range queue.Responses {
            log.Println("++++>", resp)
          }
        }()

        headers := &push.Headers{
          Topic: "com.letsapp.BundleID",
        }

        // send the notifications
        for i := 0; i < 5; i++ {
          queue.Push(deviceToken, headers, payloadData)
        }

        // done sending notifications, wait for all responses and shutdown
        queue.Wait()

      } else {
        log.Printf("init client error: %v \n", err)
      }

    } else {
      log.Printf("init client error: %v \n", err)
    }

  } else {
    log.Printf("load cert error: %v \n", err)
  }
}

What did you expect to see?

2016/07/22 22:44:36 ++++> {token id error_nil}
2016/07/22 22:44:37 ++++> {token id error_nil}
2016/07/22 22:44:38 ++++> {token id error_nil}
2016/07/22 22:44:39 ++++> {token id error_nil}
2016/07/22 22:44:40 ++++> {token id error_nil}

What did you see instead?

2016/07/22 22:44:36 ++++> {token id error_nil}
2016/07/22 22:44:37 ++++> {token id error_nil}
2016/07/22 22:44:38 ++++> {token id error_nil}
2016/07/22 22:44:39 ++++> {token id error_nil}

Production mode add apns-topic

------------------------------ push/service.go -------------------------------
index d1388e1..2ccdaee 100644
@@ -30,6 +30,7 @@ const maxPayload = 4096 // 4KB at most
type Service struct {
Host string
Client *http.Client

  • Topic string
    }

// NewService creates a new service to connect to APN.
@@ -72,6 +73,8 @@ func (s *Service) Push(deviceToken string, headers *Headers, payload []byte) (st
return "", err
}
req.Header.Set("Content-Type", "application/json")

  • //Production mode need apns-topic
  • req.Header.Set("apns-topic", s.Topic)
    headers.set(req.Header)

resp, err := s.Client.Do(req)

Provide Topic parameter

Transparent topic extraction can be problematic. One certificate can serve several topics, VoIP and debug ones. When a message is sent to a specific topic, there has to be a way to specify it.

ioutil.ReadAll considered harmful

http://jmoiron.net/blog/crossing-streams-a-love-letter-to-ioreader/ has some context on reasons to avoid a ReadAll. This fact is reflected in the current source - from the source:

    // TODO: could decode while reading instead
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    var response response
    json.Unmarshal(body, &response)

If I'm undersatnding correctly, I believe this TODO could be done by essentially doing

var response response
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
    return "", err
}

and it would be beneficial for performance

Many ESTABLISHED state to APNS, defer close not work?

By invoking a netstat -antup | grep server | awk '{print $5}' | cut -d: -f1 | sort | uniq -c command on server I got a lot occurencies of APNS IPs with ESTABLISHED state, what is going wrong?

 38 17.110.226.29
 43 17.110.227.88
 36 17.110.227.89
 33 17.110.227.90
 32 17.110.227.91
 37 17.110.227.92
 45 17.110.227.93
 45 17.110.227.94
 48 17.110.227.95
 49 17.110.227.96
 26 17.143.164.136
 19 17.143.164.137
 30 17.143.164.138
 45 17.143.164.139
 39 17.143.164.140
 26 17.143.164.141
 38 17.143.164.142
 49 17.143.164.77
 17 17.143.164.78
 46 17.143.164.79
 15 17.172.233.39
 13 17.172.234.15
  4 17.172.234.16
 15 17.172.234.17
 15 17.172.234.18
 16 17.172.234.19
 10 17.172.234.20
 12 17.172.234.21
 18 17.172.234.22
 13 17.172.234.23

Where first column is occurency of second column (APNS IP).

unexpected EOF while trying to push

Hello there,

recently I get unexpected EOF errors when I try to send notifications, 0 notification will work now.

What did you do? (steps to reproduce or a code sample is helpful)

  • Try to send a notification

What did you expect to see?

  • A notification

What did you see instead?

  • Post https://api.push.apple.com/3/device/{token}: unexpected EOF in the logs.

Is there a way to know what happened ? Shouldn't buford tell a bit more on it ??

Thanks !

Parse and return timestamp for ErrGone

The response from APNS may include a timestamp for 410 Status Gone (and possibly an ErrUnregistered JSON response?)

the value of this key is the last time at which APNs confirmed that the device token was no longer valid for the topic. Stop pushing notifications until the device registers a token with a later timestamp with your provider.

This should be parsed and returned with the error. Apple's documentation doesn't say the type or format of the timestamp. There is a good chance it is a Unix timestamp in number of seconds since the epoch, but it would be good to get this error back to ensure that is the case.

Token Authentication with JSON Web Tokens (JWT)

What's New in the Apple Push Notification Service
https://developer.apple.com/videos/play/wwdc2016/724/

Starting with a review of the HTTP/2 based provider API, you will learn about an important new feature: Token Based Authentication. Learn to connect to APNs using authentication tokens for sending pushes via the HTTP/2 API, relieving you of the overhead associated with maintaining valid certificates.

TODO:

  • Refresh my knowledge of JWT
  • Write example that uses a Go JWT library
  • Get a signing Key from Apple to test this out
  • Review the APIs and dependencies
  • Documentation of Certificate and Token authentication options

Function for customizing http.Transport for a http/2 client

Now buford provides the function of push.NewClient(cert tls.Certifcate) (*http.Client, error) for creating a http/2 client.

client, err := push.NewClient(certificate)

But push.NewClient() can not customize http.Transport like the code below for a http/2 client.

// example definition
transport := &http.Transport{
        TLSClientConfig:     config,
        MaxIdleConnsPerHost: 16,
}

MaxIdleConnsPerHost is important for sending many notifications to APNs once. Accoding to the document below (Best Practices for Managing Connections), increasing connections to APNs seems to be recommended for improving performance. (MaxIdleConnsPerHost is 2 by default. )

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/APNsProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH101-SW1

Though it is good if there is the function for customizing http.Transport for a http/2 client, what do you think?

Extract bundle-id from certificate

I've noticed that you can extract the bundle-id from the certificate and put it into the Headers as Topic (it seems to be required), so you can avoid asking for it.

cert, err := certificate.Load(filename, passphrase)
if err != nil {
    log.Fatal(err)
}

commonName := cert.Leaf.Subject.CommonName
bundle := strings.Replace(commonName, "Apple Push Services: ", "", 1)

headers := &push.Headers{
    Topic: bundle,
}

push.Error not containing DeviceToken

In the previous versions of buford, the error struct had a field for device token of the notification. This was useful since for some errors (ie. ErrUnregistered) the device token needs to be removed from the backend. In the latest version this field seems to have been removed from the error struct.
This causes an issue in the concurrent scenario (push.NewQueue( service, workers) where there is no way to track for which device the error is received and makes it hard to act on it.

panic error

panic: close of closed channel

goroutine 46 [running]:
panic(0x7c8aa0, 0xc8206dc8c0)
/home/users/tianwanli01/Code/Go/go-go1.6/src/runtime/panic.go:464 +0x3e6
golang.org/x/net/http2.(_ClientConn).streamByID(0xc820082b00, 0x100000001, 0x0)
/home/users/tianwanli01/Code/Go/gopath/src/golang.org/x/net/http2/transport.go:913 +0x101
golang.org/x/net/http2.(_ClientConn).forgetStreamID(0xc820082b00, 0xc800000001)
/home/users/tianwanli01/Code/Go/gopath/src/golang.org/x/net/http2/transport.go:904 +0x2e
golang.org/x/net/http2.(_ClientConn).RoundTrip(0xc820082b00, 0xc82060c000, 0xc8206ee480, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/gopath/src/golang.org/x/net/http2/transport.go:605 +0x600
golang.org/x/net/http2.(_Transport).RoundTripOpt(0xc8206308c0, 0xc82060c000, 0xc820045300, 0xc8204fc300, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/gopath/src/golang.org/x/net/http2/transport.go:263 +0x296
golang.org/x/net/http2.(_Transport).RoundTrip(0xc8206308c0, 0xc82060c000, 0x0, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/gopath/src/golang.org/x/net/http2/transport.go:238 +0x41
net/http.(_Transport).RoundTrip(0xc820748480, 0xc82060c000, 0xc820748480, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/go-go1.6/src/net/http/transport.go:318 +0x8e0
net/http.send(0xc82060c000, 0x7f87122d7528, 0xc820748480, 0x0, 0x0, 0x0, 0xc820634230, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/go-go1.6/src/net/http/client.go:260 +0x6b7
net/http.(_Client).send(0xc8206ef590, 0xc82060c000, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/go-go1.6/src/net/http/client.go:155 +0x185
net/http.(_Client).doFollowingRedirects(0xc8206ef590, 0xc82060c000, 0x9ff768, 0x0, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/go-go1.6/src/net/http/client.go:475 +0x8a4
net/http.(_Client).Do(0xc8206ef590, 0xc82060c000, 0xc, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/go-go1.6/src/net/http/client.go:191 +0x1e4
github.com/WanliTian/apns/vendor/github.com/RobotsAndPencils/buford/push.(_Service).PushBytes(0xc82010f760, 0xc8206fc200, 0x40, 0xc820475e78, 0xc82070a090, 0x65, 0x83, 0x0, 0x0, 0x0, ...)
/home/users/tianwanli01/Code/Go/gopath/src/github.com/WanliTian/apns/vendor/github.com/RobotsAndPencils/buford/push/service.go:173 +0x397
github.com/WanliTian/apns/vendor/github.com/RobotsAndPencils/buford/push.(_Service).Push(0xc82010f760, 0xc8206fc200, 0x40, 0xc820475e78, 0x7c7520, 0xc8207142a0, 0x0, 0x0, 0x0, 0x0)
/home/users/tianwanli01/Code/Go/gopath/src/github.com/WanliTian/apns/vendor/github.com/RobotsAndPencils/buford/push/service.go:159 +0x105
main.(_Worker).start(0xc8206d6190)

Improve error message when not using http/2 client

Hitting Apple with an HTTP/1.1 request results in the error malformed HTTP status code "client". (#26)

Importing golang.org/x/net/http2 (#27) means that GODEBUG=http2client=0 no longer disables http2. But if for some reason http2 isn't established, it would be nice to catch that sooner and report a friendly error.

Now that the http/2 library is a dependency (#27), Go 1.6 may not be a requirement. It may be possible to support Go 1.5.3 by removing the +build go1.6 build tag, but this will require testing.

push: recommendation for number of goroutines / streams

Apple allows between 400 to 4000 streams.

net/http2 is hard coded at 1000 streams (maxConcurrentStreams):
https://github.com/golang/net/blob/313cf39d4ac368181bce6960ac9be9e7cee67e68/http2/transport.go#L409 (thanks to @sideshow for pointing this out)

In addition to streams, multiple connections can be used. From Apple docs:

You can establish multiple connections to APNs servers to improve performance. When you send a large number of remote notifications, distribute them across connections to several server endpoints. This improves performance, compared to using a single connection, by letting you send remote notifications faster and by letting APNs deliver them faster.

You can check the health of your connection using an HTTP/2 PING frame. Docs

Slow sending 50000+ push

for i := 0; i < len(js.Tokens); i++ { tok := js.Tokens[i] if tok != "" { id, err := service.Push(token, nil, p) } }
Slow sending. What can you think up to speed sending?

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.