Solving PushSharp.Apple Disconnect Issue
While doing a load test of a new Apple Passbook application, I suddenly saw some 200K transmissions errors from my WebApi application. Searching the web I found that a “high” rate of connect/disconnect to Apple Push Notification Service being reported as causing APNS to do a forced disconnect.
While Apple does have a limit (very very high) on the number of notifications before they will refuse connections for an hour, the limit for connect/disconnect is much lower. After some playing around a bit I found that if I persisted the connection via a static, I no longer have this issue.
Below is a sample of the code.
- Note: we disconnect and reconnect whenever an error happens (I have not seen an error yet)
using Newtonsoft.Json.Linq;
using PushSharp.Apple;
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace RedDwarfDogs.Passbook.Engine.Notification
{
public class AppleNotification : INotification
{
private readonly IPassbookSettings _passbookSettings;
private readonly ILogger_logger;
private static ApnsServiceBroker _apnsServiceBroker;
private static object lockObject = new object();
public AppleNotification(ILogger logger,IPassbookSettings passbookSettings)
{
_logger= Guard.EnsureArgumentIsNotNull(logger, "logger");
_passbookSettings = Guard.EnsureArgumentIsNotNull(passbookSettings, "passbookSettings");
}
public void SendNotification(HashSet<string> deviceTokens)
{
if (deviceTokens == null || deviceTokens.Count == 0)
{
return;
}
try
{
_logger.Write("PassbookEngine_SendNotification_Apple");
// Create a new broker if needed
if (_apnsServiceBroker == null)
{
X509Certificate2 cert = _passbookSettings.ApplePushCertificate;
if (cert == null)
throw new InvalidOperationException("pushThumbprint certificate is not installed or has invalid Thumbprint");
var config = new ApnsConfiguration(ApnsConfiguration.ApnsServerEnvironment.Production,
_passbookSettings.ApplePushCertificate, false);
_logger.Write("PassbookEngine_SendNotification_Apple_Connect");
_apnsServiceBroker = new ApnsServiceBroker(config);
// Wire up events
_apnsServiceBroker.OnNotificationFailed += (notification, aggregateEx) =>
{
aggregateEx.Handle(ex =>
{
_logger.Write("Apple Notification Failed", "Direct", ex);
_logger.Write("PassbookEngine_SendNotification_Apple_Error");
// See what kind of exception it was to further diagnose
if (ex is ApnsNotificationException)
{
var notificationException = (ApnsNotificationException)ex;
var apnsNotification = notificationException.Notification;
var statusCode = notificationException.ErrorStatusCode;
}
_logger.Write("SendNotification", "PushToken Rejected", ex);
// We reset to null to recreate / connect
Restart();
return true;
});
};
_apnsServiceBroker.OnNotificationSucceeded += (notification) =>
{
};
// Start the broker
}
var sentTokens = new StringBuilder();
lock (lockObject)
{
_apnsServiceBroker.Start();
foreach (var deviceToken in deviceTokens)
{
if (string.IsNullOrWhiteSpace(deviceToken) || deviceToken.Length < 32 || deviceToken.Length > 256 || deviceToken.Contains("-"))
{
//Invalid Token, keep in Apple's good books
// We use GUID's thus - for faking pushtokens. Do not send them to apple
// We do not want to be get black listed
}
else
{
// Queue a notification to send
var nofification = new ApnsNotification
{
DeviceToken = deviceToken,
Payload = JObject.Parse("{\"aps\":{\"badge\":7}}")
};
try
{
_apnsServiceBroker.QueueNotification(nofification);
sentTokens.AppendFormat("{0} ", deviceToken);
}
catch (System.InvalidOperationException)
{
// Assuming already in queue
}
}
}
try
{
//duplicate signals may occur
_apnsServiceBroker.Stop();
}
catch { }
}
var auditLog = new Log
{
Message = sentTokens.ToString(),
RequestHttpMethod = "Post"
};
_logger.Write("Passbook", PassbookLogMessageCategory.SendNotification.ToString(),
"PassbookAudit", "Passbook", auditLog);
return;
}
catch (Exception exc)
{
// We swallow notification exceptions - for example APSN is off line. Allow rest of processing to work.
_logger.Write("SendNotification", "One or more notifications via Apple (APNS) failed", exc);
Restart();
_apnsServiceBroker = null; //force a reset
}
}
private void Restart()
{
if (_apnsServiceBroker != null)
{
try
{
//duplicate signals may occur
_apnsServiceBroker.Stop();
}
catch { }
_logCounterWrapper.Increment("PassbookEngine_SendNotification_Apple_Restart");
_apnsServiceBroker = null;
}
}
}
}
Comments
Post a Comment