// Send sends push notification to the APNs. func (c *PersistentClient) Send(ctx context.Context, pn *PushNotification) *PushNotificationResponse { resp := NewPushNotificationResponse(pn) payload, err := pn.ToBytes() if err != nil { resp.Success = false resp.Error = err return resp } _, err = c.Write(payload) if err != nil { resp.Success = false resp.ResponseCommand = LocalResponseCommand resp.ResponseStatus = RetryPushNotificationStatus resp.Error = err return resp } log.Println("Sending push notification with ID", pn.Identifier) // This channel will contain the raw response // from Apple in the event of a failure. responseChannel := make(chan []byte, 1) go func() { buffer := make([]byte, 6) n, err := c.Read(buffer) if n != 6 && err != nil { buffer[0] = LocalResponseCommand e, ok := err.(net.Error) switch { case err == io.EOF: // Socket has been closed buffer[1] = RetryPushNotificationStatus case ok && e.Timeout(): // There is an error and it is a timeout buffer[1] = NoErrorsStatus default: buffer[1] = UnknownErrorStatus } } responseChannel <- buffer }() select { case <-ctx.Done(): <-responseChannel // Wait for the read to end. resp.Success = false resp.ResponseCommand = LocalResponseCommand resp.ResponseStatus = CanceledPushNotificationStatus resp.Error = ctx.Err() case r := <-responseChannel: resp.FromRawAppleResponse(r) } return resp }
// httpDo issues the HTTP request and calls f with the response. If ctx.Done is // closed while the request or f is running, httpDo cancels the request, waits // for f to exit, and returns ctx.Err. Otherwise, httpDo returns f's error. func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { // Run the HTTP request in a goroutine and pass the response to f. tr := &http.Transport{} client := &http.Client{Transport: tr} c := make(chan error, 1) go func() { c <- f(client.Do(req)) }() select { case <-ctx.Done(): tr.CancelRequest(req) <-c // Wait for f to return. return ctx.Err() case err := <-c: return err } }
// Call sends the request, waits for it to complete, and returns its error status. func (client *Client) Call(ctx context.Context, req proto.Message, resp proto.Message) error { // Setup a new call call := &Call{ Req: req, Resp: resp, cancelc: make(chan interface{}), } // Make sure a service name is passed if serviceName, ok := ServiceNameFromContext(ctx); !ok { return ErrServiceNameMissing } else { call.Service = serviceName } // If a manual deadline is not passed, setup with default timeout if _, ok := ctx.Deadline(); !ok { ctx, _ = context.WithDeadline(ctx, time.Now().Add(RequestTimeout)) } // Run the request in a goroutine and write the reponse to c c := make(chan error, 1) go func() { c <- client.send(ctx, call) }() select { // Use context done to manually trigger cancel case <-ctx.Done(): // Cancel the request in flight call.cancelc <- true <-c // Wait for the request to finish if ctx.Err() == context.DeadlineExceeded { return ErrTimeout } return ctx.Err() // Request finished case err := <-c: return err } }