func apnsresToError(apnsres *common.APNSResult, psp *push.PushServiceProvider, dp *push.DeliveryPoint) push.PushError { var err push.PushError switch apnsres.Status { case 0: err = nil case 1: err = push.NewBadDeliveryPointWithDetails(dp, "Processing Error") case 2: err = push.NewBadDeliveryPointWithDetails(dp, "Missing Device Token") case 3: err = push.NewBadNotificationWithDetails("Missing topic") case 4: err = push.NewBadNotificationWithDetails("Missing payload") case 5: err = push.NewBadNotificationWithDetails("Invalid token size") case 6: err = push.NewBadNotificationWithDetails("Invalid topic size") case 7: err = push.NewBadNotificationWithDetails("Invalid payload size") case 8: // err = NewBadDeliveryPointWithDetails(req.dp, "Invalid Token") // This token is invalid, we should unsubscribe this device. err = push.NewUnsubscribeUpdate(psp, dp) default: err = push.NewErrorf("Unknown Error: %d", apnsres.Status) } return err }
func admSinglePush(psp *push.PushServiceProvider, dp *push.DeliveryPoint, data []byte, notif *push.Notification) (string, push.PushError) { client := &http.Client{} req, err := admNewRequest(psp, dp, data) if err != nil { return "", err } defer req.Body.Close() resp, httpErr := client.Do(req) if httpErr != nil { return "", push.NewErrorf("Failed to send adm push: %v", httpErr.Error()) } defer resp.Body.Close() id := resp.Header.Get("x-amzn-RequestId") if resp.StatusCode != 200 { if resp.StatusCode == 503 || resp.StatusCode == 500 || resp.StatusCode == 429 { // By default, we retry after one minute. retryAfter := resp.Header.Get("Retry-After") retrySecond := 60 if retryAfter != "" { var retryErr error retrySecond, retryErr = strconv.Atoi(retryAfter) if retryErr != nil { retrySecond = 60 } } retryDuration := time.Duration(retrySecond) * time.Second err = push.NewRetryError(psp, dp, notif, retryDuration) return id, err } body, ioErr := ioutil.ReadAll(resp.Body) if ioErr != nil { return "", push.NewErrorf("Failed to read adm response: %v", err) } var fail admPushFailResponse jsonErr := json.Unmarshal(body, &fail) if jsonErr != nil { return "", push.NewErrorf("%v: %v", resp.StatusCode, string(body)) } reason := strings.ToLower(fail.Reason) switch reason { case "messagetoolarge": err = push.NewBadNotificationWithDetails("MessageTooLarge") case "invalidregistrationid": err = push.NewBadDeliveryPointWithDetails(dp, "InvalidRegistrationId") case "accesstokenexpired": // retry would fix it. err = push.NewRetryError(psp, dp, notif, 10*time.Second) default: err = push.NewErrorf("%v: %v", resp.StatusCode, fail.Reason) } return "", err } return id, nil }
func admURL(dp *push.DeliveryPoint) (url string, err push.PushError) { if dp == nil { err = push.NewError("nil dp") return } if regid, ok := dp.FixedData["regid"]; ok { url = fmt.Sprintf("%v%v/messages", admServiceURL, regid) } else { err = push.NewBadDeliveryPointWithDetails(dp, "empty delivery point") } return }
// Push will read all of the delivery points to send to from dpQueue and send responses on resQueue before closing the channel. If the notification data is invalid, // it will send only one response. func (self *pushService) Push(psp *push.PushServiceProvider, dpQueue <-chan *push.DeliveryPoint, resQueue chan<- *push.PushResult, notif *push.Notification) { defer close(resQueue) // Profiling // self.updateCheckPoint("") var err push.PushError req := new(common.PushRequest) req.PSP = psp req.Payload, err = toAPNSPayload(notif) if err == nil && len(req.Payload) > self.requestProcessor.GetMaxPayloadSize() { err = push.NewBadNotificationWithDetails(fmt.Sprintf("payload is too large: %d > %d", len(req.Payload), self.requestProcessor.GetMaxPayloadSize())) } if err != nil { res := new(push.PushResult) res.Provider = psp res.Content = notif res.Err = push.NewErrorf("Failed to create push: %v", err) resQueue <- res for _ = range dpQueue { } return } unixNow := uint32(time.Now().Unix()) expiry := unixNow + 60*60 if ttlstr, ok := notif.Data["ttl"]; ok { ttl, err := strconv.ParseUint(ttlstr, 10, 32) if err == nil { expiry = unixNow + uint32(ttl) } } req.Expiry = expiry req.Devtokens = make([][]byte, 0, 10) dpList := make([]*push.DeliveryPoint, 0, 10) for dp := range dpQueue { res := new(push.PushResult) res.Destination = dp res.Provider = psp res.Content = notif devtoken, ok := dp.FixedData["devtoken"] if !ok { res.Err = push.NewBadDeliveryPointWithDetails(dp, "NoDevtoken") resQueue <- res continue } btoken, err := hex.DecodeString(devtoken) if err != nil { res.Err = push.NewBadDeliveryPointWithDetails(dp, err.Error()) resQueue <- res continue } req.Devtokens = append(req.Devtokens, btoken) dpList = append(dpList, dp) } n := len(req.Devtokens) lastId := self.getMessageIds(n) req.MaxMsgId = lastId req.DPList = dpList // We send this request object to be processed by pushMux goroutine, to send responses/errors back. errChan := make(chan push.PushError) resChan := make(chan *common.APNSResult, n) req.ErrChan = errChan req.ResChan = resChan self.requestProcessor.AddRequest(req) // errChan closed means the message(s) is/are sent successfully to the APNs. // However, we may have not yet receieved responses from APNS - those are sent on resChan for err = range errChan { res := new(push.PushResult) res.Provider = psp res.Content = notif if _, ok := err.(*push.ErrorReport); ok { res.Err = push.NewErrorf("Failed to send payload to APNS: %v", err) } else { res.Err = err } resQueue <- res } // Profiling // self.updateCheckPoint("sending the message takes") if err != nil { return } for i, dp := range dpList { if dp != nil { r := new(push.PushResult) r.Provider = psp r.Content = notif r.Destination = dp mid := req.GetId(i) r.MsgId = fmt.Sprintf("apns:%v-%v", psp.Name(), mid) r.Err = nil resQueue <- r } } // Wait for the unserialized responses from APNS asynchronously - these will not affect what we send our clients for this request, but will affect subsequent requests. go self.waitResults(psp, dpList, lastId, resChan) }