timehop / apns Goto Github PK
View Code? Open in Web Editor NEWA Go package to interface with the Apple Push Notification Service
Home Page: https://godoc.org/github.com/timehop/apns
License: MIT License
A Go package to interface with the Apple Push Notification Service
Home Page: https://godoc.org/github.com/timehop/apns
License: MIT License
@bdotdub So far I haven't been able to figure out why the two tests on Travis CI are failing. They are both tests that have a bad push.
client_test.go:
https://travis-ci.org/timehop/apns
I added some logging into RunLoop where the 1 second sleep is. As it turns out, neither of these tests hit that (several other tests did log errors here, probably intentionally - invalid cert, etc).
Let's write some tests for #11.
I'd like some tests for errors from Apple but without actually hitting Apple's servers in the test suite.
Status code | Description |
---|---|
0 | No errors encountered |
1 | Processing error |
2 | Missing device token |
3 | Missing topic |
4 | Missing payload |
5 | Invalid token size |
6 | Invalid topic size |
7 | Invalid payload size |
8 | Invalid token |
10 | Shutdown |
255 | None (unknown) |
I'm thinking about mockConn having a special device token that results in 8 Invalid token, or that sort of thing. A bit like test credit cards.
My thought is to only do this for tests within the apns package, not impacting the callers of this library in any way.
Hey guys, thanks for this awesome piece of work, this is definitely one of the best APNS packages out there for go.
I'm not really sure if I'm using the feedback service correctly because it sort of never prints any errors, even if I test it with broken/custom device tokens. I would love to detect expired tokens and delete them to keep my data clean and only send pushes to valid devices.
This is how I use it:
func (n *NotificationAgent) sendPushMessages(devices []model.Device,
t NotificationType, args NotificationArguments, payload Payload) {
for _, device := range devices {
if *device.Type == model.DeviceTypeIos {
p := apns.NewPayload()
p.APS.Alert.Body = n.getLocalizedPushText(t, args)
p.APS.Badge.Set(1)
p.SetCustomValue("type", t)
p.SetCustomValue("data", payload)
m := apns.NewNotification()
m.Payload = p
m.DeviceToken = *device.Token
m.Priority = apns.PriorityImmediate
if e := n.apns.Send(m); e != nil {
log.Println("Error: " + e.Error())
}
}
}
for ft := range n.apnsf.Receive() {
log.Println("Feedback for token:" + ft.DeviceToken)
}
}
As mentioned above I tried to use random device tokens in the belief that it would print something, but nothing happens. I'm not even sure if I'm calling the feedback service correctly because the sending is asynchronous and maybe my feedback loop is called too early... if so, where and how should I call it?
Oh and, yes n.apnsf
is correctly initialized and throws no errors. I just wanted to keep the code small.
Thanks,
codingrogue
If I have something like:
client := [...]
go func() {
for f := range client.FailedNotifs {
[...]
}
}()
for [...] {
[...]
[...] client.Send(n) [...]
[...]
}
// done, now what?
How do I clean up? I don't see any code that closes client.FailedNotifs, so that goroutine won't stop unless I signal it myself (if I even know it exists). It's also strange to be expected to close client.Conn yourself; this requires intimate familiarity with the client implementation instead of just its interface.
It would be great if Client had a Close() error
method, or something comparable.
Certificates exported from the Apple keychain are in DER format (*.p12 files).
It would make sense to support these directly (in addition to or instead of PEM formatted files). The library I've been using for this is: https://github.com/Azure/go-pkcs12
There is an effort underway to move that library into x/crypto and firm up the APIs. Azure/go-pkcs12#9
Similar to, but not a duplicate of, #3. #3 is about not knowing the reason a client has disconnected; this issue is about not knowing the reason the feedback service disconnected.
I'm currently experiencing issues connecting to the feedback service, but my gateway is working fine. I haven't been able to inspect the reason from the lib.
Hey there,
Sorry if this is a stupid question but I did not see anywhere where I can enter the pass phrase for the key file.
Without it when I try to use NewClientWithFiles method, it just gives me the following error.
2016/01/13 11:16:32 Could not create clientcrypto/tls: failed to parse private key
I tried my cert and key file with openssl client and they worked fine. I am also using the same files on the production with my api (php).
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : AES256-SHA
Session-ID:
Session-ID-ctx:
Master-Key: 0C21677365B5EE5EA621FE5C3464C8B750A574E1ED632D34AF683121305054F4C12409D672C1B9F4BFCD89685671AA13
Key-Arg : None
Start Time: 1452678448
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
asdasdasd
closed
When receiving an error-response packet with a status code of 0 (No errors encountered)
, does it just ignore that?
I'm thinking that I'd like to send "success" over FaildNotifs (probably renamed). Essentially, I'm trying to wrap a synchronous API around this low-level socket stuff so that my REST API can return an appropriate status code.
Right now I'd be waiting on FailedNotif with a timeout, and if nothing shows up, assume success.
I'd probably keep that behaviour, but if I get back 0 (No errors encountered)
, I can stop waiting for the timeout and just return 200 OK right away.
What do you think?
It's clear that:
TCP_CORK
-like implementation of sending APNS messagesIn my mind, these are all closely intertwined with throughput. Any of these can be solved very easily at the expense of throughput. With high throughput in mind, these become more challenging to address. :)
This issue is a teaser. I have some ideas, but I'm going to wait until we're finished nailing down #31 before opening that bucket of worms. That way we can avoid the API moving every which way at once.
Forgive me,
@tylrtrmbl
package main
import (
"fmt"
"github.com/timehop/apns"
)
func main() {
c, _ := apns.NewClient("gateway.sandbox.push.apple.com:2195", "dev_ck.pem", "dev_pk.pem")
p := apns.NewPayload()
p.APS.Alert.Body = "I am a push notification!"
p.APS.Badge.Set(5)
p.APS.Sound = "turn_down_for_what.aiff"
m := apns.NewNotification()
m.Payload = p
m.DeviceToken = "A_DEVICE_TOKEN"
m.Priority = apns.PriorityImmediate
resp := c.Send(m)
fmt.Printf("%v\n",resp)
}
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send (nil chan)]:
main.main()
D:/go/bin/project/src/applePush/applyPush.go:21 +0x1b2
exit status 2
``
Since we switched to using this library in production, we've had many reports of dropped push notifications. I think it's because of the persistent connection, which may at times error and then disconnect. Sounds like they're trying to handle this in https://github.com/pranavraja/apns, though that looks like less mature.
Any thoughts on how to detect/avoid this, or any recommendations? Appreciate the library and the help.
Thanks,
Ben
Hi,
It's really nice to find a package that offer persistent connection to APNS server. We are trying to get this into our notification system.
We have 1 extra need, which is sending extra keys in the notification body. It's similar to the added acme2 key in this example.
Do you think this would be a good fit? I am already working on it at in viki-org/apns, based on your master branch.
c.Send(m) isn't returning and I'm not sure why. I created a client without error and am not receiving any errors on sent PNs.
c, err := apns.NewClientWithFiles(apns.SandboxGateway, CERT_PEM, KEY_PEM)
if err != nil {
log.Println("Client error: ", err.Error())
}
go func() {
for f := range c.FailedNotifs {
log.Println("Notif", f.Notif.ID, " failed with ", f.Err.Error())
}
}()
p := apns.NewPayload()
p.APS.Alert.Body = content
badge := 1
p.APS.Badge = &badge
p.APS.Sound = "bingbong.aiff"
m := apns.NewNotification()
m.Payload = p
m.DeviceToken = author.PnId
m.Priority = apns.PriorityImmediate
m.ID = content
log.Println("---")
c.Send(m)
log.Println("...")
In order to test code that uses this client, I would have to mock net.Conn, if I understand correctly. This is onerous; mocking the client's API is much easier. Exporting a mockable client interface, and an implementation of it, helps with this.
Something along these lines...
type Client interface {
Send(n Notification) error
Conn() *Conn
FailedNotifs() chan NotificationResult
}
type client struct {[...]} // implements Client
func NewClient(gw string, cert string, key string) (Client, error) {...}
so we can test code like this:
func Foo(client apns.Client) error {
[...]
if err := client.Send([...]); err != nil {
return err
}
[...]
}
like this:
func TestFoo(t *testing.T) {
mymock := MyMock{} // has type apns.Client
mymock.When("Send", apnsClient.Notification{[...]}).Return(errors.New("test"))
if Foo(mymock) == nil {
t.Error("Foo should return apns error")
}
}
It would be pretty sweet if I could write "integration" tests for my own code that don't hit Apple's API.
Thoughts?
"mutable-content" key support not available for Rich push notification. Any updates ?
Is there a provision for the apns.Client
to notify the application when an APNS connection has been lost? I'm thinking I have a bug where I cannot connect to the APNS gateway, but I'm unable to get any visibility into the package if the connection is succeeding or not.
I noticed the run loop seems to be an infinite loop. Is there anyway we can send a command to gracefully shutdown the server?
@bdotdub Would it be possible to add some additional maintainers to this project?
#14 Has been open since December 10th. It has tests, it received code review and updates based on review. While it changes behaviour in certain instances, I don't think it would be considered a breaking change.
It's difficult to contribute further while these pull requests remain open.
There were some comments from #41 that hinted we may wish to revisit error types. I don't have anything specific in mind today, but recommend we review error handling specifically after some of the more major API changes we're considering.
The only problem with using a series of pointers in structs is they can end in race conditions if any goroutines mutate the underlying pointer.
For that reason, I took the same approach here for
omitempty
badges: joekarl/go-libapns#8It's not "pretty" but it's thread-safe. @themartorana
Excuse my ignorance.. I'm new to the iOS world.
I'm getting the error: "crypto/tls: failed to parse certificate PEM data" from this code:
c, err := apns.NewClient(apns.SandboxGateway, CERT_PEM, KEY_PEM)
if err != nil {
println("ERROR!!!!: ", err.Error())
}
p := apns.NewPayload()
p.APS.Alert.Body = content
badge := 1
p.APS.Badge = &badge
p.APS.Sound = "bingbong.aiff"
m := apns.NewNotification()
m.Payload = p
m.DeviceToken = author.PnId
m.Priority = apns.PriorityImmediate
c.Send(m)
where CERT_PEM ("devcert.pem") and KEY_PEM ("devkey.pem") are the filenames. The code above is being called from a directory below the files:
project/
folder/
sendAPN.go
devcert.pem
devkey.pem
I created the CERT_PEM by using this command:
openssl x509 -in aps_development.cer -inform der -out devcert.pem
and I created KEY_PEM using this command:
openssl pkcs12 -nocerts -out devkey.pem -in key.p12
I successfully tested these by using this command:
openssl s_client -connect gateway.sandbox.push.apple.com:2195
-cert devcert.pem -key devkey.pem
What am I doing wrong here? Thanks a bunch for the library.
When Failednotifs has objects, do I have to use the send command to attempt to resend?
I think it's because it's a struct. One thought is to make it a pointer, but that adds some complexity to the user.
If we wanted to avoid complexity for the user, we could do our own serialization entirely to make sure the alert isn't serialized unless it needs to be. We could probably do that as a method on the Alert itself, which would be neat. That adds complexity to the lib though.
Is there a batch limit I need to adher to?
I sent it to 1000 folks, each push note was c.Send and it processed them quite fast which was rad but at around 500 it just stopped sending. I then tried processing without a c.Send and it went through all the device tokens and complained at the end that there were too many connections.
I can run it correctly in local app engine environment, but when I deployed on the Google app engine, it won't work. Any idea?
This line: https://github.com/timehop/apns/blob/master/notification.go#L50
Since it is a *int
you can no longer assign int
to it directly. This is probably due to the pointer handling changes in the recently released go1.4.
I think the docs needs updating to reflect this.
https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html states in part:
The binary interface employs a plain TCP socket for binary content that is streaming in nature. For optimum performance, batch multiple notifications in a single transmission over the interface, either explicitly or using a TCP/IP Nagle algorithm.
Was net.TCPConn.SetNoDelay
= false
considered? The quotation seems to indicate doing this would yield better performance.
The tests seem to be flaky. Before I made any changes, a few tests would sometimes time out, but other times succeed. There should be a longer test time out than 1 second, it seems.
As the title says, since I've read from the docs that it might periodically disconnect any idle APN client. Is this managed automatically and we should only create one client? Or should we create new client each time we have a payload to deliver?
Same goes for the feedback loop. Do we just fire one loop? Or do we have to continually fire one as we create new client?
Send ok.
feedback error:Could not create feedback crypto/tls: failed to parse certificate PEM data
client, err := apns.NewClientWithFiles(apns.ProductionGateway, "resource/cer.pem", "resource/key.unencrypted.pem")
if nil != err {
util.NewLog().Info(err.Error())
}
//设置样式
payload := apns.NewPayload()
payload.APS.Alert.Body = "hello,world2222"
payload.APS.Sound = "bingbong.aiff"
m := apns.NewNotification()
m.Payload = payload
m.DeviceToken = "00d7c5d3d764b4eef67096f562748c2ee11939bddffed866f453ca745249fdaa"
client.Send(m)
f, err := apns.NewFeedback(apns.ProductionFeedbackGateway,"resource/cer.pem", "resource/key.unencrypted.pem")
if err != nil {
fmt.Println("Could not create feedback", err.Error())
}
for ft := range f.Receive() {
fmt.Println("Feedback for token:", ft.DeviceToken)
}
Might as well remove the error from the signature.
I just spent a long time tracking down a bug where I was passing a byte slice for the device identifier instead of a hex encoded string. I took an awfully long time, since I didn't have an error come out of Client.FailedNotifs
.
We should do that. 😊
I'm currently working on a REST API wrapper around APNs. I've been trying to figure out how to handle the FailedNotifs channel, but I'm finding it difficult to send a request that causes an error.
I've tried fiddling around in ToBinary to send an invalid notification, but it ends up with an err.Identifier
that doesn't match the notification identifier (err.Identifier just binary gibberish).
I'm wondering how you use FailedNotifs in production? Any suggestions?
I don't understand why APS.MarshalJSON is defined:
func (aps APS) MarshalJSON() ([]byte, error) {
data := make(map[string]interface{})
if !aps.Alert.isZero() {
data["alert"] = aps.Alert
}
if aps.Badge != nil {
data["badge"] = aps.Badge
}
if aps.Sound != "" {
data["sound"] = aps.Sound
}
if aps.ContentAvailable != 0 {
data["content-available"] = aps.ContentAvailable
}
if aps.Category != "" {
data["category"] = aps.Category
}
if aps.URLArgs != nil && len(aps.URLArgs) != 0 {
data["url-args"] = aps.URLArgs
}
return json.Marshal(data)
}
This appears to almost duplicate the default marshaling behavior for json struct tags. The only difference I can see is with Alert, where Alert.Title/Action don't affect whether Alert is included, and I don't understand why. Perhaps I'm missing something?
Hi all,
I've shared these ideas with @bdotdub and gone over parts with @nathany now as well. I just found my notes and figured I'd type them here on GitHub as well.
The ideas I will lay out below was guided by some factors from the community:
Here's what I came up with:
The current Connection
struct in master
is simply a net.Conn
wrapper with some configuration. This isn't the same as a mockable interface, and is probably insufficient. In develop
, Connection
handles three things:
io.ReadWriteCloser
)I posit that if we move the responsibility for reconnection into the Client
, we can get rid of the Connection
altogether and simply use io.ReadWriteCloser
. It would work this way:
Client
detects a connection (io.ReadWriteCloser
) has gone sour, it gets a new one from a provider functiontls.Certificate
type ConnectionProvider func(gateway string, cert tls.Certificate) (io.ReadWriteCloser, error)
type Client struct {
...
Gateway string
Cert tls.Certificate
NewConnection ConnectionProvider
...
}
func DefaultConnectionProvider(gateway string, cert tls.Certificate) (tls.Conn, err Error) {
gatewayParts := strings.Split(gw, ":")
config := tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: gatewayParts[0],
}
return tls.Dial("tcp", gateway, config)
}
func NewClient(gateway, cert tls.Config) *Client{
return &Client{
Gateway: gateway,
Cert: cert,
NewConnection: DefaultConnectionProvider,
}
}
func Example() {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
panic("cannot load APNS cert")
}
client := NewClient(ProductionGateway, cert)
...
}
With this scheme, the Client is able to make a new connection whenever it needs one. The user is able to replace NewConnection
to provide a custom-configured connection, as well as replace NewConnection
to provide a mock connection. DefaultConnectionProvider
is exported so it can be used if the user wants override only a few settings from the default connection.
Let me know what you guys think!
For a little inspiration, I'm taking a look at other Go projects for APNS, starting with the one @finkel mentioned in #47 and then the one @themartorana mentioned (#38). @willfaught mentioned anachronistic in #26 (comment).
SendOne, MakeNotification, and ReadInvalid make for a nice lower-level API that other things can be built on. I'm not sure how I feel about the queue. If I was fanning-out the same/similar message to a number of devices or using it as a mechanism for batch & send, maybe it could work, but managing multiple queues at once (requeuing on errors while sending more messages) could become cumbersome for the client.
This relates to #10, as I try to build a synchronous API around Send and FailedNotif.
To get an error from Apple, I am changing a single digit in an otherwise valid device token. I've added logging to timehop/apns.
On this push (with invalid data) nothing comes back from Apple:
2014/12/17 11:32:26 Apple gateway at gateway.sandbox.push.apple.com:2195
2014/12/17 11:32:28 pushing payload...
2014/12/17 11:32:28 json: {"aps":{"alert":{"body":"hello robots"}}}
2014/12/17 11:32:29 Timed out waiting for errors (FailedNotifs). Must be good.
That's not great, but okay. On this push, errrors are coming back from Apple, but the identifiers don't match up so FailedNotifs are never sent.
2014/12/17 11:32:31 pushing payload...
2014/12/17 11:32:31 json: {"aps":{"alert":{"body":"hello robots"}}}
2014/12/17 11:32:31 handleError n.Identifier=0 err.Identifier=2 err=&apns.Error{Command:0x8, Status:0x8, Identifier:0x2, ErrStr:"Invalid token"}
2014/12/17 11:32:31 handleError n.Identifier=0 err.Identifier=2 err=&apns.Error{Command:0x8, Status:0x8, Identifier:0x2, ErrStr:"Invalid token"}
2014/12/17 11:32:32 Timed out waiting for errors (FailedNotifs). Must be good.
If Identifier is set, then I do see an identifier match and the error is sent via FailedNotif:
2014/12/17 11:35:02 pushing payload...
2014/12/17 11:35:02 json: {"aps":{"alert":{"body":"hello robots"}}}
2014/12/17 11:35:02 handleError n.Identifier=1234 err.Identifier=1234 err=&apns.Error{Command:0x8, Status:0x8, Identifier:0x4d2, ErrStr:"Invalid token"}
2014/12/17 11:35:02 reportFailedPush apns.Notification{ID:"user:timestamp", DeviceToken:"30c4afb...", Identifier:0x4d2, Expiration:(*time.Time)(0xc2080bc018), Priority:10, Payload:(*apns.Payload)(0xc2082488c0)} true
2014/12/17 11:35:02 Notif user:timestamp failed with {8 8 1234 Invalid token}
It would be useful to have the possibility of setting some socket options from the application while initializing a Client.
For example I had to deploy a low-traffic notification gateway on Google Cloud Platform. All the instances have a private IP, NATed on a public one. The NAT timeouts after 10 mins of TCP inactivity.
I forked your repo, please see what I mean here: zaninime@5f8cba1
Does this package support sending multiple notifications In The same transmission?
Nice to have for mocking/testing.
Hey y'all!
Sorry for the radio silence on a bunch of the issues & PRs. As mentioned in #17, I've been thinking about how to make it a bit more synchronous to make the data flow so that the error reporting and handling isn't as roundabout. This feels like the root of the problem for #4, #19, and #25.
In the current implementation, it feels like I've conflated multiple concepts into a single Client
struct. Right now, the Client
maintains the whole lifecycle of the interactions with Apple (connection, reconnection, requeueing, etc). While Client
has an arguably convenient interface, it doesn't allow for granular control for what to do for connection retry strategies, reconnection behavior (ie. do we automatically requeue all failed notifs?), etc.
On the branch I've been working on (Branch — Comparison to master), I've added a separate Session
concept. The state of the branch is that most of the code is there, and I'm now just filling in the rest of the tests.
At a high level, here is what changed:
Conn
is now an interface so that it can be mockable.Session
. Client
is now only responsible for holding a Conn
and then create Sessions
to interact with. Client
holds the higher level logic of creating a new Session
on disconnect and requeueing undelivered notifs. By extracting out the Session
bits, it'll be much easier to be able to provide ways to change reconnection policies, strategies for how to deal with undelivered notifs, etcIn theory, you could easily create your own Client
with your own behavior by using Session
if you didn't like the one in this lib.
These changes have also helped the testability and brittleness of the previous tests. They should now be running consistently on Travis.
I'd love to get all of your thoughts on this!
cc @brunodecarvalho @tylrtrmbl @nathany @willfaught
In the meantime, I'll take a look at the PRs currently open
Apple announced a new provider API coming later in 2015 that is based on HTTP/2.
https://developer.apple.com/videos/wwdc/2015/?id=720 (at about 20 minutes)
This uses a request/response cycle with standard http status codes. The connection is left open after a notification failed, and there is no need to resend notifications. There is also no need for a separate feedback service.
There will be no need to keep the existing APNS provider API as the new API applies to older devices running existing versions of iOS and OS X. The video mentions some other changes, such as 4K payloads and ~100-character device tokens.
when we send to many devices with the same message, is there a method to make it called once ?
There are a number of things I would like to see:
Separate loading certificates from configuring the Client or Feedback service, and add support for *.p12 DER certificates as exported from the Apple keychain. #37
Separate the serialization and binary encoding of payloads from actual sending.
Provide (optional) validation that should reduce the chance of error responses from Apple.
Mock tests for when Apple does have an error response, #40.
Change ErrProcessing, etc to errors instead of strings. Error Response shouldn't need Command (8) or Status.
Move away from using pointers for omitempty when serializing JSON #38
Handle retry logic differently for cases like revoked or invalid certificates (if those can be checked before connecting, that would be great too).
Allow closing the client #25, for example if the certificate expired and a new connection is needed (though in that case it may close due to an error).
Reevaluate how notifications are stored and resent. Currently there is a buffer of 50 messages, which may not be enough. Rather than a maximum, another option would be to assume success after a given time period (or some combination with a much higher maximum). Also, messages aren't requeued until the next Send (on develop).
There is also a cleanup thread running that constantly checks the oldest notification in the ‘Sent’ queue to see if it’s older than a few seconds and if so, it is assumed to have been successfully sent and is removed from the ‘Sent’ queue. - The Problem With Apples Push Notification Service
It can take a while for the dropped connection to make its way from APNs back to your server just because of normal latency. It's possible to send over 500 notifications before a write fails because of the connection being dropped. Around 1,700 notifications writes can fail just because the pipe is full, so just retry in that case once the stream is ready for writing again. - Push Notification Throughput and Error Checking
Maybe storing/resending notifications needs to be extracted and optional so that in-memory isn't the only option.
Remove determineIdentifier and require ID to be set explicitly by the caller #19. Or provide an identifier generator that must be called explicitly rather than passing no ID.
Ensure caller is aware of Error responses even if it can't be mapped to a recently sent Notification (SessionError).
Hi,
When I ran the tests for 135f567, I saw this
$ go test
Running Suite: Apns Suite
=========================
Random Seed: 1431423856
Will run 72 of 72 specs
••••••••••••••••••••••••••••••••••••••••••••
------------------------------
•... Timeout [1.000 seconds]
Client
/home/mark/workspace/src/apns/client_test.go:384
#Send
/home/mark/workspace/src/apns/client_test.go:383
bad push
/home/mark/workspace/src/apns/client_test.go:181
should not return an error [It]
/home/mark/workspace/src/apns/client_test.go:180
Timed out
/home/mark/workspace/src/apns/client_test.go:180
------------------------------
••
------------------------------
•... Timeout [1.000 seconds]
Client
/home/mark/workspace/src/apns/client_test.go:384
#Send
/home/mark/workspace/src/apns/client_test.go:383
good, bad, good, requeue of last good
/home/mark/workspace/src/apns/client_test.go:382
should not return an error [It]
/home/mark/workspace/src/apns/client_test.go:381
Timed out
/home/mark/workspace/src/apns/client_test.go:381
------------------------------
••••••••••••••••••••••••
Summarizing 2 Failures:
[Timeout...] Client #Send bad push [It] should not return an error
/home/mark/workspace/src/apns/client_test.go:180
[Timeout...] Client #Send good, bad, good, requeue of last good [It] should not return an error
/home/mark/workspace/src/apns/client_test.go:381
Ran 72 of 72 Specs in 5.489 seconds
FAIL! -- 70 Passed | 2 Failed | 0 Pending | 0 Skipped --- FAIL: TestApns (5.49s)
FAIL
exit status 1
FAIL apns 6.554s
For the first case ("#Send -> bad push -> should not return an error), I guess it's because this line block: https://github.com/timehop/apns/blob/master/client_test.go#L169
It's probably something wrong with my setup. Have you seen this before?
var placeholder = [5]byte{}
func (n Notification) ToBinary() ([]byte, error) {
....
/* write header place holder*/
buf.Write(placeholder[:])
/* write fields: token,payload... etc */
...
/* rewrite header */
b = buf.Bytes()
b[0] = commandID
binary.BigEndian.PutUint32(b[1:5], uint32(len(b)-5))
return b, nil
}
but seems to mostly be used as a value. E.g. NotificationResult.Err is an Error, not an *Error.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.