Skip to content

Add google apns proxy support #790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions PushSharp.Apple/ApnsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;

Expand Down Expand Up @@ -82,7 +83,7 @@ void Initialize (ApnsServerEnvironment serverEnvironment, X509Certificate2 certi

FeedbackIntervalMinutes = 10;
FeedbackTimeIsUTC = false;

AdditionalCertificates = new List<X509Certificate2> ();
AddLocalAndMachineCertificateStores = false;

Expand All @@ -100,7 +101,6 @@ void Initialize (ApnsServerEnvironment serverEnvironment, X509Certificate2 certi
InternalBatchFailureRetryCount = 1;
}


void CheckIsApnsCertificate ()
{
if (Certificate != null) {
Expand Down Expand Up @@ -136,6 +136,22 @@ public void OverrideFeedbackServer (string host, int port)
FeedbackPort = port;
}

public void SetProxy(string proxyHost, int proxyPort)
{
UseProxy = true;
ProxyHost = proxyHost;
ProxyPort = proxyPort;
ProxyCredentials = CredentialCache.DefaultNetworkCredentials;
}

public void SetProxy(string proxyHost, int proxyPort, string userName, string password, string domain)
{
UseProxy = true;
ProxyHost = proxyHost;
ProxyPort = proxyPort;
ProxyCredentials = new NetworkCredential(userName, password, domain);
}

public string Host { get; private set; }

public int Port { get; private set; }
Expand All @@ -144,6 +160,14 @@ public void OverrideFeedbackServer (string host, int port)

public int FeedbackPort { get; private set; }

public bool UseProxy { get; private set; }

public string ProxyHost { get; private set; }

public int ProxyPort { get; private set; }

public NetworkCredential ProxyCredentials { get; private set; }

public X509Certificate2 Certificate { get; private set; }

public List<X509Certificate2> AdditionalCertificates { get; private set; }
Expand Down Expand Up @@ -205,4 +229,4 @@ public enum ApnsServerEnvironment {
Production
}
}
}
}
86 changes: 64 additions & 22 deletions PushSharp.Apple/ApnsConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
using System.Collections.Generic;
using System.Threading;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using PushSharp.Core;
using System.Diagnostics;

namespace PushSharp.Apple
{
Expand Down Expand Up @@ -68,7 +72,6 @@ public ApnsConnection (ApnsConfiguration configuration)
byte[] buffer = new byte[6];
int id;


SemaphoreSlim connectingSemaphore = new SemaphoreSlim (1);
SemaphoreSlim batchSendSemaphore = new SemaphoreSlim (1);
object notificationBatchQueueLock = new object ();
Expand Down Expand Up @@ -127,40 +130,56 @@ async Task SendBatch ()

Log.Info ("APNS-Client[{0}]: Sending Batch ID={1}, Count={2}", id, batchId, toSend.Count);

try {
try
{

var data = createBatch (toSend);

if (data != null && data.Length > 0) {
if (data != null && data.Length > 0)
{

for (var i = 0; i <= Configuration.InternalBatchFailureRetryCount; i++) {
for (var i = 0; i <= Configuration.InternalBatchFailureRetryCount; i++)
{

await connectingSemaphore.WaitAsync ();

try {
try
{
// See if we need to connect
if (!socketCanWrite () || i > 0)
await connect ();
} finally {
}
finally
{
connectingSemaphore.Release ();
}

try {
try
{
await networkStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
break;
} catch (Exception ex) when (i != Configuration.InternalBatchFailureRetryCount) {
}
catch (Exception ex) when (i != Configuration.InternalBatchFailureRetryCount)
{
Log.Info("APNS-CLIENT[{0}]: Retrying Batch: Batch ID={1}, Error={2}", id, batchId, ex);
}
}

foreach (var n in toSend)
sent.Add (new SentNotification (n));
}

} catch (Exception ex) {
}
catch (ApnsConnectionException ex)
{
Log.Error ("APNS-CLIENT[{0}]: Send Batch Error: Batch ID={1}, Error={2}", id, batchId, ex);
foreach (var n in toSend)
n.CompleteFailed (new ApnsNotificationException (ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex));
return;
}
catch (Exception ex) {
Log.Error ("APNS-CLIENT[{0}]: Send Batch Error: Batch ID={1}, Error={2}", id, batchId, ex);
foreach (var n in toSend)
n.CompleteFailed(new ApnsNotificationException(ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex));
}

Log.Info ("APNS-Client[{0}]: Sent Batch, waiting for possible response...", id);
Expand Down Expand Up @@ -239,7 +258,7 @@ async Task Reader ()
sent.Clear ();
return;
}

// If we make it here, we did get data back, so we have errors

Log.Info ("APNS-Client[{0}]: Batch (ID={1}) completed with error response...", id, batchId);
Expand Down Expand Up @@ -321,25 +340,48 @@ async Task connect ()

Log.Info ("APNS-Client[{0}]: Connecting (Batch ID={1})", id, batchId);

client = new TcpClient ();
client = new TcpClient();

try {
await client.ConnectAsync (Configuration.Host, Configuration.Port).ConfigureAwait (false);
try
{
if (!Configuration.UseProxy)
{
await client.ConnectAsync (Configuration.Host, Configuration.Port).ConfigureAwait (false);
}
else
{
var proxyHelper = new ProxyHelper { ProxyConnectionExceptionCreator = (message) => new ApnsConnectionException(message) };
proxyHelper.BeforeConnect += () => Log.Info("APNS-Client[{0}]: Connecting Proxy (Batch ID={1})", id, batchId);
proxyHelper.AfterConnect += (status) => Log.Info("APNS-Client[{0}]: Proxy Connected (Batch ID={1}) : {2}", id, batchId, status);
await proxyHelper.Connect(client, Configuration.Host, Configuration.Port, Configuration.ProxyHost, Configuration.ProxyPort, Configuration.ProxyCredentials).ConfigureAwait(false);
}

//Set keep alive on the socket may help maintain our APNS connection
try {
client.Client.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
} catch {
try
{
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
}
catch
{
}

//Really not sure if this will work on MONO....
// This may help windows azure users
try {
SetSocketKeepAliveValues (client.Client, (int)Configuration.KeepAlivePeriod.TotalMilliseconds, (int)Configuration.KeepAliveRetryPeriod.TotalMilliseconds);
} catch {
try
{
SetSocketKeepAliveValues(client.Client, (int)Configuration.KeepAlivePeriod.TotalMilliseconds, (int)Configuration.KeepAliveRetryPeriod.TotalMilliseconds);
}
} catch (Exception ex) {
throw new ApnsConnectionException ("Failed to Connect, check your firewall settings!", ex);
catch
{
}
}
catch (ApnsConnectionException)
{
throw;
}
catch (Exception ex)
{
throw new ApnsConnectionException("Failed to Connect, check your firewall settings!", ex);
}

// We can configure skipping ssl all together, ie: if we want to hit a test server
Expand Down
90 changes: 65 additions & 25 deletions PushSharp.Apple/ApnsFeedbackService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using PushSharp.Core;

namespace PushSharp.Apple
{
Expand All @@ -24,6 +25,11 @@ public FeedbackService (ApnsConfiguration configuration)
public event FeedbackReceivedDelegate FeedbackReceived;

public void Check ()
{
this.GetTokenExpirations();

}
public IEnumerable<ApnsTokeExpirationInfo> GetTokenExpirations()
{
var encoding = Encoding.ASCII;

Expand All @@ -32,9 +38,22 @@ public void Check ()
var certificates = new X509CertificateCollection();
certificates.Add(certificate);

var client = new TcpClient (Configuration.FeedbackHost, Configuration.FeedbackPort);
var client = new TcpClient();
Log.Info("APNS-FeedbackService: Connecting");
if (Configuration.UseProxy)
{
var proxyHelper = new ProxyHelper { ProxyConnectionExceptionCreator = (message) => new ApnsConnectionException(message) };
proxyHelper.BeforeConnect += () => Log.Info("APNS-FeedbackService: Connecting Proxy");
proxyHelper.AfterConnect += (status) => Log.Info("APNS-FeedbackService: Proxy Connected : {0}", status);
proxyHelper.Connect(client, Configuration.FeedbackHost, Configuration.FeedbackPort, Configuration.ProxyHost, Configuration.ProxyPort, Configuration.ProxyCredentials).Wait();
}
else
{
client.Connect(Configuration.FeedbackHost, Configuration.FeedbackPort);
}
Log.Info("APNS-FeedbackService: Connected");

var stream = new SslStream (client.GetStream(), true,
var stream = new SslStream(client.GetStream(), true,
(sender, cert, chain, sslErrs) => { return true; },
(sender, targetHost, localCerts, remoteCert, acceptableIssuers) => { return certificate; });

Expand All @@ -44,83 +63,104 @@ public void Check ()
//Set up
byte[] buffer = new byte[4096];
int recd = 0;
var data = new List<byte> ();
var data = new List<byte>();

Log.Info("APNS-FeedbackService: Getting expirations");

//Get the first feedback
recd = stream.Read(buffer, 0, buffer.Length);

var tokenBatch = new List<ApnsTokeExpirationInfo>();
//Continue while we have results and are not disposing
while (recd > 0)
{
// Add the received data to a list buffer to work with (easier to manipulate)
for (int i = 0; i < recd; i++)
data.Add (buffer [i]);
data.Add(buffer[i]);

//Process each complete notification "packet" available in the buffer
while (data.Count >= (4 + 2 + 32)) // Minimum size for a valid packet
{
var secondsBuffer = data.GetRange (0, 4).ToArray ();
var tokenLengthBuffer = data.GetRange (4, 2).ToArray ();
var secondsBuffer = data.GetRange(0, 4).ToArray();
var tokenLengthBuffer = data.GetRange(4, 2).ToArray();

// Get our seconds since epoch
// Check endianness and reverse if needed
if (BitConverter.IsLittleEndian)
Array.Reverse (secondsBuffer);
var seconds = BitConverter.ToInt32 (secondsBuffer, 0);
Array.Reverse(secondsBuffer);
var seconds = BitConverter.ToInt32(secondsBuffer, 0);

//Add seconds since 1970 to that date, in UTC
var timestamp = new DateTime (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds (seconds);
var timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds);

//flag to allow feedback times in UTC or local, but default is local
if (!Configuration.FeedbackTimeIsUTC)
timestamp = timestamp.ToLocalTime();


if (BitConverter.IsLittleEndian)
Array.Reverse (tokenLengthBuffer);
var tokenLength = BitConverter.ToInt16 (tokenLengthBuffer, 0);
Array.Reverse(tokenLengthBuffer);
var tokenLength = BitConverter.ToInt16(tokenLengthBuffer, 0);

if (data.Count >= 4 + 2 + tokenLength) {
if (data.Count >= 4 + 2 + tokenLength)
{

var tokenBuffer = data.GetRange (6, tokenLength).ToArray ();
var tokenBuffer = data.GetRange(6, tokenLength).ToArray();
// Strings shouldn't care about endian-ness... this shouldn't be reversed
//if (BitConverter.IsLittleEndian)
// Array.Reverse (tokenBuffer);
var token = BitConverter.ToString (tokenBuffer).Replace ("-", "").ToLower ().Trim ();
var token = BitConverter.ToString(tokenBuffer).Replace("-", "").ToLower().Trim();

// Remove what we parsed from the buffer
data.RemoveRange (0, 4 + 2 + tokenLength);
data.RemoveRange(0, 4 + 2 + tokenLength);

tokenBatch.Add(new ApnsTokeExpirationInfo(token, timestamp));
// Raise the event to the consumer
var evt = FeedbackReceived;
if (evt != null)
evt (token, timestamp);
} else {
evt(token, timestamp);

}
else
{
continue;
}

}

//Read the next feedback
recd = stream.Read (buffer, 0, buffer.Length);
recd = stream.Read(buffer, 0, buffer.Length);
}

try
{
stream.Close ();
stream.Close();
stream.Dispose();
}
catch { }

try
try
{
client.Client.Shutdown (SocketShutdown.Both);
client.Client.Dispose ();
client.Client.Shutdown(SocketShutdown.Both);
client.Client.Dispose();
}
catch { }

try { client.Close (); } catch { }
try { client.Close(); } catch { }

Log.Info("APNS-FeedbackService: {0} expiration(s) received.", tokenBatch.Count);
return tokenBatch;
}
}

public class ApnsTokeExpirationInfo
{
public ApnsTokeExpirationInfo(string token, DateTime timestamp)
{
this.Token = token;
this.Timestamp = timestamp;
}

public string Token { get; private set; }
public DateTime Timestamp { get; private set; }
}
}
Loading