func main() { cert, pemErr := certificate.FromPemFile("../cert.pem", "") if pemErr != nil { log.Println("Cert Error:", pemErr) } notification := &apns.Notification{} notification.DeviceToken = "11aa01229f15f0f0c52029d8cf8cd0aeaf2365fe4cebc4af26cd6d76b7919ef7" notification.Topic = "com.sideshow.Apns2" notification.Payload = []byte(`{ "aps" : { "alert" : "Hello!" } } `) client := apns.NewClient(cert).Development() res, err := client.Push(notification) if err != nil { log.Println("Error:", err) return } log.Println("APNs ID:", res.ApnsID) }
func Start() { LogInfo("Push proxy server is initializing...") if len(CfgPP.ApplePushCertPrivate) > 0 { appleCert, appleCertErr := certificate.FromPemFile(CfgPP.ApplePushCertPrivate, CfgPP.ApplePushCertPassword) if appleCertErr != nil { LogCritical(fmt.Sprintf("Failed to load the apple pem cert err=%v", appleCertErr)) } if CfgPP.ApplePushUseDevelopment { appleClient = apns.NewClient(appleCert).Development() } else { appleClient = apns.NewClient(appleCert).Production() } } else { LogError("Apple push notifications not configured. Mssing ApplePushCertPrivate.") } if len(CfgPP.AndroidApiKey) == 0 { LogError("Android push notifications not configured. Mssing AndroidApiKey.") } router := mux.NewRouter() var handler http.Handler = router vary := throttled.VaryBy{} vary.RemoteAddr = false vary.Headers = strings.Fields(CfgPP.ThrottleVaryByHeader) th := throttled.RateLimit(throttled.PerSec(CfgPP.ThrottlePerSec), &vary, throttledStore.NewMemStore(CfgPP.ThrottleMemoryStoreSize)) th.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { LogError(fmt.Sprintf("%v: code=429 ip=%v", r.URL.Path, GetIpAddress(r))) throttled.DefaultDeniedHandler.ServeHTTP(w, r) }) handler = th.Throttle(router) router.HandleFunc("/", root).Methods("GET") r := router.PathPrefix("/api/v1").Subrouter() r.HandleFunc("/send_push", handleSendNotification).Methods("POST") go func() { err := manners.ListenAndServe(CfgPP.ListenAddress, handler) if err != nil { LogCritical(err.Error()) } }() LogInfo("Server is listening on " + CfgPP.ListenAddress) }
func main() { kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version("0.1").Author("Alisson Sales") kingpin.CommandLine.Help = `Listens to STDIN to send nofitications and writes APNS response code and reason to STDOUT. The expected format is: <DeviceToken> <APNS Payload> Example: aff0c63d9eaa63ad161bafee732d5bc2c31f66d552054718ff19ce314371e5d0 {"aps": {"alert": "hi"}}` kingpin.Parse() cert, pemErr := certificate.FromPemFile(*certificatePath, "") if pemErr != nil { log.Fatalf("Error retrieving certificate `%v`: %v", certificatePath, pemErr) } client := apns2.NewClient(cert) if *mode == "development" { client.Development() } else { client.Production() } scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { in := scanner.Text() notificationArgs := strings.SplitN(in, " ", 2) token := notificationArgs[0] payload := notificationArgs[1] notification := &apns2.Notification{ DeviceToken: token, Topic: *topic, Payload: payload, } res, err := client.Push(notification) if err != nil { log.Fatal("Error: ", err) } else { fmt.Printf("%v: '%v'\n", res.StatusCode, res.Reason) } } }
/** * An APN Worker which reads from RabbitMQ queue and sends APN messages * If APN is expired or invalid it sends APN key to a queue which is processed by another goroutine and updated in database */ func apn_processor(identity int, config Configuration, conn *amqp.Connection, ApnStatusInactiveQueueName, ApnQueueName string, ch_err, ch_apn_log_success chan []byte, logger *log.Logger, killWorker chan int, apnQueue ApnQueue) { isHourly := apnQueue.IsHourly pemPath := apnQueue.PemPath apnTopic := apnQueue.Topic // Load PEM file specified in config file required to send APN messages cert, pemErr := certificate.FromPemFile(pemPath, "") failOnError(pemErr, "Failed to load PEM file for APN") ApnQueueNameOriginal := ApnQueueName now := time.Now() var hourlyTick <-chan time.Time if isHourly == true { curHour := "" curHour = strconv.Itoa(now.Hour()) curHourInt := now.Hour() if curHourInt < 10 { curHour = "0" + strconv.Itoa(curHourInt) } else { curHour = strconv.Itoa(curHourInt) } //tmp := now.Second()%24 //if tmp < 10 { // curHour = "0" + strconv.Itoa(tmp) //} else { // curHour = strconv.Itoa(tmp) //} ApnQueueName = ApnQueueNameOriginal + "_" + curHour waitTime := 60 - now.Minute() hourlyTick = time.After(time.Duration(waitTime*61) * time.Second) //fmt.Println(waitTime%24) //hourlyTick = time.After(time.Duration(waitTime%24) * time.Second) olog(fmt.Sprintf("Connecting to "+ApnQueueName), config.DebugMode) } // Create a new APN Client client := apns.NewClient(cert).Production() // Create new channel to communicate with RabbitMQ ch, err := conn.Channel() failOnError(err, "Failed to open a channel") defer ch.Close() err = ch.Qos( 1, // prefetch count 0, // prefetch size false, // global ) failOnError(err, "Failed to set QoS") msgsApn, err := ch.Consume( ApnQueueName, // queue "", // consumer false, // auto-ack false, // exclusive false, // no-local false, // no-wait nil, // args ) failOnError(err, "Failed to register a consumer") // Get a message // Process it // If processing is complete, then delete it for { select { case <-hourlyTick: olog(fmt.Sprintf("Ticking"), config.DebugMode) curHour := "" now = time.Now() curHourInt := now.Hour() if curHourInt < 10 { curHour = "0" + strconv.Itoa(curHourInt) } else { curHour = strconv.Itoa(curHourInt) } ch.Cancel(ApnQueueName, false) curHour = strconv.Itoa(now.Hour()) ApnQueueName = ApnQueueNameOriginal + "_" + curHour msgsApn, err = ch.Consume( ApnQueueName, // queue "", // consumer false, // auto-ack false, // exclusive false, // no-local false, // no-wait nil, // args ) failOnError(err, "Failed to register a consumer") olog(fmt.Sprintf("Connected to "+ApnQueueName), config.DebugMode) waitTime := 60 - now.Minute() hourlyTick = time.After(time.Duration(waitTime*61) * time.Second) case <-killWorker: // If a message is receieved through killWorker then log and exit olog(fmt.Sprintf("%d Received kill command", identity), config.DebugMode) return case d, ok := <-msgsApn: // Receieve messages from RabbitMQ, if !ok { // If channel is closed then wait for it to be reconnected or wait for kill signal time.Sleep(100 * time.Millisecond) continue } olog(fmt.Sprintf("%d Worker Received a message: %s", identity, d.Body), config.DebugMode) // Receive APN message from channel and decode it payload := ApnMessage{} err := json.Unmarshal(d.Body, &payload) // In case message coult not be decoded log error and skip if err != nil { logger.Printf("Unmarshal error = %s, for MQ message data = %s", err.Error(), d.Body) olog(fmt.Sprintf("Unmarshal error = %s, for MQ message data = %s", err.Error(), d.Body), config.DebugMode) // Acknowledge to MQ that work has been processed successfully d.Ack(false) continue } // Extract Body and Token from Payload data := payload.Body token := payload.Token // Create new APN notification notification := &apns.Notification{} // Add device token notification.DeviceToken = token notification.Topic = apnTopic notification.Payload, err = json.Marshal(data) // If config contains time to love for message then add it if apnQueue.TtlSeconds > 0 { notification.Expiration = time.Now().Add(time.Duration(apnQueue.TtlSeconds) * time.Second) } // If json data contains ttl value then override it if payload.TimeToLiveSeconds > 0 { notification.Expiration = time.Now().Add(time.Duration(payload.TimeToLiveSeconds) * time.Second) } // Could not encode map to json. if err != nil { logger.Printf("Marshal error = %s, for MQ message data = %s", err.Error(), d.Body) olog(fmt.Sprintf("Marshal error = %s, for MQ message data = %s", err.Error(), d.Body), config.DebugMode) d.Ack(false) continue } res, err := client.Push(notification) if err != nil { // gcmErrCount++ notificationByteArray, _ := json.Marshal(notification) logger.Printf("APN send error = %s, data=%s", err.Error(), notificationByteArray) olog(fmt.Sprintf("APN send error = %s", err.Error()), config.DebugMode) // In case of APN error / Requeue and continue to next // TODO: Inform admin // Same error continues without break for 10 times, sleep for a minute // TODO: Implement this in a better way // if gcmErrCount >= 10 { // time.Sleep(time.Minute) // } key := token val, ok := retries_apn.Get(key) var valint int if ok { valint, ok = val.(int) if ok { valint = valint + 1 } else { // Log error (This should not happen) and continue logger.Printf("APN Could not convert interface to int = %v", val) olog(fmt.Sprintf("APN Could not convert interface to int = %v", val), config.DebugMode) d.Ack(false) continue } } else { valint = 1 } // If already tried 5 times, then do not requeue if valint >= config.APN.RequeueCount+1 { // Log it and continue logger.Printf("Max Retries APN send data=%s", notificationByteArray) olog(fmt.Sprintf("Max Retries APN send error. Data = %s", notificationByteArray), config.DebugMode) retries_apn.Remove(key) d.Ack(false) continue } retries_apn.Set(key, valint) d.Nack(false, true) continue } else { // gcmErrCount = 0 // TODO: Implement this as proper goroutine, its dangereous if user exists before this goroutine ends // Running result processor as a goroutine so that current worker can proceed with sending another APN request to // server, without getting delayed by processing go func() { isSentToClientSuccesfully := StatusErrApnError t := time.Now() ts := t.Format(time.RFC3339) if res.StatusCode == 200 { // Success (Log to file at bottom) isSentToClientSuccesfully = StatusSuccessApnRequest } else if res.Reason == "BadDeviceToken" || res.Reason == "Unregistered" { // Token error, set status of this token as inactive in database // Create a Status Inactive Message to be sent to RabbitMQ queue statusInactiveMsg := ApnStatusInactiveMsg{Token: token} // Convert it into json byte array jsonStatusInactiveMsg, err := json.Marshal(statusInactiveMsg) // If there is an issue while conversion, log it and skip if err != nil { logger.Printf("APN status inactive Marshal error = %s", err.Error()) olog(fmt.Sprintf("APN status inactive Marshal error = %s", err.Error()), config.DebugMode) } else { // Publish message to RabbitMQ err = ch.Publish( "", // exchange ApnStatusInactiveQueueName, // routing key false, // mandatory false, amqp.Publishing{ DeliveryMode: amqp.Persistent, ContentType: "text/json", Body: jsonStatusInactiveMsg, }) // If there is error while publishing message log it if err != nil { logger.Printf("APN status inactive Publish error = %s", err.Error()) olog(fmt.Sprintf("APN status inactive Publish error = %s", err.Error()), config.DebugMode) } } // Error is logged automatically after this block } else if res.Reason == "TooManyRequests" { // TODO: Delay sending instead of skipping // Error is logged automatically after this block } apnLog, err := json.Marshal(ApnLog{TimeStamp: ts, Type: isSentToClientSuccesfully, ApnId: token, Data: ApnError{Reason: res.Reason}}) if err != nil { logger.Printf("Marshal error while logging APN response = %s", err.Error()) olog(fmt.Sprintf("Marshal error while logging APN response = %s", err.Error()), config.DebugMode) } if isSentToClientSuccesfully == StatusSuccessApnRequest { ch_apn_log_success <- apnLog } else { ch_err <- apnLog } }() } // Acknowledge to MQ that work has been processed successfully d.Ack(false) } } }
func TestNoCertificatePemFile(t *testing.T) { cer, err := certificate.FromPemFile("_fixtures/certificate-no-certificate.pem", "") assert.Equal(t, tls.Certificate{}, cer) assert.Equal(t, certificate.ErrNoCertificate, err) }
func TestNoKeyPemFile(t *testing.T) { cer, err := certificate.FromPemFile("_fixtures/certificate-no-key.pem", "") assert.Equal(t, tls.Certificate{}, cer) assert.Equal(t, certificate.ErrNoPrivateKey, err) }
func TestBadKeyPemFile(t *testing.T) { cer, err := certificate.FromPemFile("_fixtures/certificate-bad-key.pem", "") assert.Equal(t, tls.Certificate{}, cer) assert.Equal(t, certificate.ErrFailedToParsePKCS1PrivateKey, err) }
func TestBadPasswordPemFile(t *testing.T) { cer, err := certificate.FromPemFile("_fixtures/certificate-valid-encrypted.pem", "badpassword") assert.Equal(t, tls.Certificate{}, cer) assert.Equal(t, certificate.ErrFailedToDecryptKey, err) }
func TestNoSuchFilePemFile(t *testing.T) { cer, err := certificate.FromPemFile("", "") assert.Equal(t, tls.Certificate{}, cer) assert.Equal(t, errors.New("open : no such file or directory").Error(), err.Error()) }
func TestEncryptedValidCertificateFromPemFile(t *testing.T) { cer, err := certificate.FromPemFile("_fixtures/certificate-valid-encrypted.pem", "password") assert.NoError(t, err) assert.Nil(t, verifyHostname(cer)) }