Skip to content

Commit 5fed317

Browse files
Caezarymitch-tofi
authored andcommitted
add APNS and GCM proxy support (#1)
* Update PushShart using a Proxy when connecting to Google Cloud Messaging (GCM) * Options for Proxy Usage Adds properties and methods to use an HTTP-proxy in your connection, either with default network credentials or specified credentials. * Connection via proxy A method to tunnel the TCP-connection via a pre-configured HTTP-Proxy has been added. If Configuration.UseProxy is set to true, a CONNECT-request is sent using the proxy adress specified. The socket is then extracted from the stream of this request's response (using the System.Reflection-Library) and used to create a TcpClient-object. All traffic sent through this TcpClient-object will pass through the proxy used to send the initial CONNECT-Request. NOTE: The method 'getClientViaHTTPProxy' uses code that was not written by the author of this commit. The original code can be found here: https://web.archive.org/web/20160317134733/https://nitormobiledevelopment.wordpress.com/2013/08/13/push-sharp-using-proxy/ * Update ApnsConnection.cs * Update ApnsConfiguration.cs * apns wit proxy withou socket hijack * Handling proxy connection errors on APNS * Using System.Net missing in GcmConfiguration * Http Proxy with Basic authentication without WebRequest in APNS * System.Net was missing in GcmConfiguration * Using http proxy with basic auth (not using socket hijacking) in APNS * New class ProxyHelper to permit proxy connect in any place using a TcpClient * Using ProxyHelper in ApnsConnection * Supporting proxy in ApnsFeedbackService. * Changes in ApnsFeedbackService to return the tokens list intead of raising events * add proxy usage section to readme
1 parent 1431f5b commit 5fed317

8 files changed

Lines changed: 321 additions & 54 deletions

File tree

‎PushSharp.Apple/ApnsConfiguration.cs‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
3+
using System.Net;
34
using System.Security.Cryptography.X509Certificates;
45
using System.Text.RegularExpressions;
56

@@ -82,7 +83,7 @@ void Initialize (ApnsServerEnvironment serverEnvironment, X509Certificate2 certi
8283

8384
FeedbackIntervalMinutes = 10;
8485
FeedbackTimeIsUTC = false;
85-
86+
8687
AdditionalCertificates = new List<X509Certificate2> ();
8788
AddLocalAndMachineCertificateStores = false;
8889

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

103-
104104
void CheckIsApnsCertificate ()
105105
{
106106
if (Certificate != null) {
@@ -136,6 +136,22 @@ public void OverrideFeedbackServer (string host, int port)
136136
FeedbackPort = port;
137137
}
138138

139+
public void SetProxy(string proxyHost, int proxyPort)
140+
{
141+
UseProxy = true;
142+
ProxyHost = proxyHost;
143+
ProxyPort = proxyPort;
144+
ProxyCredentials = CredentialCache.DefaultNetworkCredentials;
145+
}
146+
147+
public void SetProxy(string proxyHost, int proxyPort, string userName, string password, string domain)
148+
{
149+
UseProxy = true;
150+
ProxyHost = proxyHost;
151+
ProxyPort = proxyPort;
152+
ProxyCredentials = new NetworkCredential(userName, password, domain);
153+
}
154+
139155
public string Host { get; private set; }
140156

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

145161
public int FeedbackPort { get; private set; }
146162

163+
public bool UseProxy { get; private set; }
164+
165+
public string ProxyHost { get; private set; }
166+
167+
public int ProxyPort { get; private set; }
168+
169+
public NetworkCredential ProxyCredentials { get; private set; }
170+
147171
public X509Certificate2 Certificate { get; private set; }
148172

149173
public List<X509Certificate2> AdditionalCertificates { get; private set; }
@@ -205,4 +229,4 @@ public enum ApnsServerEnvironment {
205229
Production
206230
}
207231
}
208-
}
232+
}

‎PushSharp.Apple/ApnsConnection.cs‎

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
using System.Collections.Generic;
88
using System.Threading;
99
using System.Net;
10+
using System.Reflection;
11+
using System.Security.Cryptography;
12+
using System.Text;
1013
using PushSharp.Common;
14+
using System.Diagnostics;
1115

1216
namespace PushSharp.Apple
1317
{
@@ -68,7 +72,6 @@ public ApnsConnection (ApnsConfiguration configuration)
6872
byte[] buffer = new byte[6];
6973
int id;
7074

71-
7275
SemaphoreSlim connectingSemaphore = new SemaphoreSlim (1);
7376
SemaphoreSlim batchSendSemaphore = new SemaphoreSlim (1);
7477
object notificationBatchQueueLock = new object ();
@@ -127,40 +130,56 @@ async Task SendBatch ()
127130

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

130-
try {
133+
try
134+
{
131135

132136
var data = createBatch (toSend);
133137

134-
if (data != null && data.Length > 0) {
138+
if (data != null && data.Length > 0)
139+
{
135140

136-
for (var i = 0; i <= Configuration.InternalBatchFailureRetryCount; i++) {
141+
for (var i = 0; i <= Configuration.InternalBatchFailureRetryCount; i++)
142+
{
137143

138144
await connectingSemaphore.WaitAsync ();
139145

140-
try {
146+
try
147+
{
141148
// See if we need to connect
142149
if (!socketCanWrite () || i > 0)
143150
await connect ();
144-
} finally {
151+
}
152+
finally
153+
{
145154
connectingSemaphore.Release ();
146155
}
147156

148-
try {
157+
try
158+
{
149159
await networkStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
150160
break;
151-
} catch (Exception ex) when (i != Configuration.InternalBatchFailureRetryCount) {
161+
}
162+
catch (Exception ex) when (i != Configuration.InternalBatchFailureRetryCount)
163+
{
152164
Log.Info("APNS-CLIENT[{0}]: Retrying Batch: Batch ID={1}, Error={2}", id, batchId, ex);
153165
}
154166
}
155167

156168
foreach (var n in toSend)
157169
sent.Add (new SentNotification (n));
158170
}
159-
160-
} catch (Exception ex) {
171+
}
172+
catch (ApnsConnectionException ex)
173+
{
161174
Log.Error ("APNS-CLIENT[{0}]: Send Batch Error: Batch ID={1}, Error={2}", id, batchId, ex);
162175
foreach (var n in toSend)
163176
n.CompleteFailed (new ApnsNotificationException (ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex));
177+
return;
178+
}
179+
catch (Exception ex) {
180+
Log.Error ("APNS-CLIENT[{0}]: Send Batch Error: Batch ID={1}, Error={2}", id, batchId, ex);
181+
foreach (var n in toSend)
182+
n.CompleteFailed(new ApnsNotificationException(ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex));
164183
}
165184

166185
Log.Info ("APNS-Client[{0}]: Sent Batch, waiting for possible response...", id);
@@ -239,7 +258,7 @@ async Task Reader ()
239258
sent.Clear ();
240259
return;
241260
}
242-
261+
243262
// If we make it here, we did get data back, so we have errors
244263

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

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

324-
client = new TcpClient ();
343+
client = new TcpClient();
325344

326-
try {
327-
await client.ConnectAsync (Configuration.Host, Configuration.Port).ConfigureAwait (false);
345+
try
346+
{
347+
if (!Configuration.UseProxy)
348+
{
349+
await client.ConnectAsync (Configuration.Host, Configuration.Port).ConfigureAwait (false);
350+
}
351+
else
352+
{
353+
var proxyHelper = new ProxyHelper { ProxyConnectionExceptionCreator = (message) => new ApnsConnectionException(message) };
354+
proxyHelper.BeforeConnect += () => Log.Info("APNS-Client[{0}]: Connecting Proxy (Batch ID={1})", id, batchId);
355+
proxyHelper.AfterConnect += (status) => Log.Info("APNS-Client[{0}]: Proxy Connected (Batch ID={1}) : {2}", id, batchId, status);
356+
await proxyHelper.Connect(client, Configuration.Host, Configuration.Port, Configuration.ProxyHost, Configuration.ProxyPort, Configuration.ProxyCredentials).ConfigureAwait(false);
357+
}
328358

329359
//Set keep alive on the socket may help maintain our APNS connection
330-
try {
331-
client.Client.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
332-
} catch {
360+
try
361+
{
362+
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
363+
}
364+
catch
365+
{
333366
}
334367

335368
//Really not sure if this will work on MONO....
336369
// This may help windows azure users
337-
try {
338-
SetSocketKeepAliveValues (client.Client, (int)Configuration.KeepAlivePeriod.TotalMilliseconds, (int)Configuration.KeepAliveRetryPeriod.TotalMilliseconds);
339-
} catch {
370+
try
371+
{
372+
SetSocketKeepAliveValues(client.Client, (int)Configuration.KeepAlivePeriod.TotalMilliseconds, (int)Configuration.KeepAliveRetryPeriod.TotalMilliseconds);
340373
}
341-
} catch (Exception ex) {
342-
throw new ApnsConnectionException ("Failed to Connect, check your firewall settings!", ex);
374+
catch
375+
{
376+
}
377+
}
378+
catch (ApnsConnectionException)
379+
{
380+
throw;
381+
}
382+
catch (Exception ex)
383+
{
384+
throw new ApnsConnectionException("Failed to Connect, check your firewall settings!", ex);
343385
}
344386

345387
// We can configure skipping ssl all together, ie: if we want to hit a test server

‎PushSharp.Apple/ApnsFeedbackService.cs‎

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Net.Security;
99
using System.Security.Cryptography;
1010
using System.Security.Cryptography.X509Certificates;
11+
using PushSharp.Common;
1112

1213
namespace PushSharp.Apple
1314
{
@@ -24,6 +25,11 @@ public FeedbackService (ApnsConfiguration configuration)
2425
public event FeedbackReceivedDelegate FeedbackReceived;
2526

2627
public void Check ()
28+
{
29+
this.GetTokenExpirations();
30+
31+
}
32+
public IEnumerable<ApnsTokeExpirationInfo> GetTokenExpirations()
2733
{
2834
var encoding = Encoding.ASCII;
2935

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

35-
var client = new TcpClient (Configuration.FeedbackHost, Configuration.FeedbackPort);
41+
var client = new TcpClient();
42+
Log.Info("APNS-FeedbackService: Connecting");
43+
if (Configuration.UseProxy)
44+
{
45+
var proxyHelper = new ProxyHelper { ProxyConnectionExceptionCreator = (message) => new ApnsConnectionException(message) };
46+
proxyHelper.BeforeConnect += () => Log.Info("APNS-FeedbackService: Connecting Proxy");
47+
proxyHelper.AfterConnect += (status) => Log.Info("APNS-FeedbackService: Proxy Connected : {0}", status);
48+
proxyHelper.Connect(client, Configuration.FeedbackHost, Configuration.FeedbackPort, Configuration.ProxyHost, Configuration.ProxyPort, Configuration.ProxyCredentials).Wait();
49+
}
50+
else
51+
{
52+
client.Connect(Configuration.FeedbackHost, Configuration.FeedbackPort);
53+
}
54+
Log.Info("APNS-FeedbackService: Connected");
3655

37-
var stream = new SslStream (client.GetStream(), true,
56+
var stream = new SslStream(client.GetStream(), true,
3857
(sender, cert, chain, sslErrs) => { return true; },
3958
(sender, targetHost, localCerts, remoteCert, acceptableIssuers) => { return certificate; });
4059

@@ -44,83 +63,104 @@ public void Check ()
4463
//Set up
4564
byte[] buffer = new byte[4096];
4665
int recd = 0;
47-
var data = new List<byte> ();
66+
var data = new List<byte>();
67+
68+
Log.Info("APNS-FeedbackService: Getting expirations");
4869

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

73+
var tokenBatch = new List<ApnsTokeExpirationInfo>();
5274
//Continue while we have results and are not disposing
5375
while (recd > 0)
5476
{
5577
// Add the received data to a list buffer to work with (easier to manipulate)
5678
for (int i = 0; i < recd; i++)
57-
data.Add (buffer [i]);
58-
79+
data.Add(buffer[i]);
80+
5981
//Process each complete notification "packet" available in the buffer
6082
while (data.Count >= (4 + 2 + 32)) // Minimum size for a valid packet
6183
{
62-
var secondsBuffer = data.GetRange (0, 4).ToArray ();
63-
var tokenLengthBuffer = data.GetRange (4, 2).ToArray ();
84+
var secondsBuffer = data.GetRange(0, 4).ToArray();
85+
var tokenLengthBuffer = data.GetRange(4, 2).ToArray();
6486

6587
// Get our seconds since epoch
6688
// Check endianness and reverse if needed
6789
if (BitConverter.IsLittleEndian)
68-
Array.Reverse (secondsBuffer);
69-
var seconds = BitConverter.ToInt32 (secondsBuffer, 0);
90+
Array.Reverse(secondsBuffer);
91+
var seconds = BitConverter.ToInt32(secondsBuffer, 0);
7092

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

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

78100

79101
if (BitConverter.IsLittleEndian)
80-
Array.Reverse (tokenLengthBuffer);
81-
var tokenLength = BitConverter.ToInt16 (tokenLengthBuffer, 0);
102+
Array.Reverse(tokenLengthBuffer);
103+
var tokenLength = BitConverter.ToInt16(tokenLengthBuffer, 0);
82104

83-
if (data.Count >= 4 + 2 + tokenLength) {
105+
if (data.Count >= 4 + 2 + tokenLength)
106+
{
84107

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

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

117+
tokenBatch.Add(new ApnsTokeExpirationInfo(token, timestamp));
94118
// Raise the event to the consumer
95119
var evt = FeedbackReceived;
96120
if (evt != null)
97-
evt (token, timestamp);
98-
} else {
121+
evt(token, timestamp);
122+
123+
}
124+
else
125+
{
99126
continue;
100127
}
101-
102128
}
103129

104130
//Read the next feedback
105-
recd = stream.Read (buffer, 0, buffer.Length);
131+
recd = stream.Read(buffer, 0, buffer.Length);
106132
}
107133

108134
try
109135
{
110-
stream.Close ();
136+
stream.Close();
111137
stream.Dispose();
112138
}
113139
catch { }
114140

115-
try
141+
try
116142
{
117-
client.Client.Shutdown (SocketShutdown.Both);
118-
client.Client.Dispose ();
143+
client.Client.Shutdown(SocketShutdown.Both);
144+
client.Client.Dispose();
119145
}
120146
catch { }
121147

122-
try { client.Close (); } catch { }
148+
try { client.Close(); } catch { }
123149

150+
Log.Info("APNS-FeedbackService: {0} expiration(s) received.", tokenBatch.Count);
151+
return tokenBatch;
124152
}
125153
}
154+
155+
public class ApnsTokeExpirationInfo
156+
{
157+
public ApnsTokeExpirationInfo(string token, DateTime timestamp)
158+
{
159+
this.Token = token;
160+
this.Timestamp = timestamp;
161+
}
162+
163+
public string Token { get; private set; }
164+
public DateTime Timestamp { get; private set; }
165+
}
126166
}

0 commit comments

Comments
 (0)