diff --git a/PushSharp.Windows/WnsConnection.cs b/PushSharp.Windows/WnsConnection.cs index 6ab72c43..e50436f5 100644 --- a/PushSharp.Windows/WnsConnection.cs +++ b/PushSharp.Windows/WnsConnection.cs @@ -1,15 +1,13 @@ using System; -using PushSharp.Core; -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; using System.Collections.Generic; -using Newtonsoft.Json.Linq; -using System.Xml; +using System.IO; using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; -using System.IO; +using System.Threading.Tasks; +using PushSharp.Core; namespace PushSharp.Windows { @@ -40,10 +38,15 @@ public WnsServiceBroker (WnsConfiguration configuration) : base (new WnsServiceC public class WnsServiceConnection : IServiceConnection { + readonly HttpClient http; + public WnsServiceConnection (WnsConfiguration configuration, WnsAccessTokenManager accessTokenManager) { AccessTokenManager = accessTokenManager; Configuration = configuration; + + http = new HttpClient (); + http.DefaultRequestHeaders.ExpectContinue = false; //Disable expect-100 to improve latency } public WnsAccessTokenManager AccessTokenManager { get; private set; } @@ -57,110 +60,109 @@ public async Task Send (WnsNotification notification) //https://cloud.notify.windows.com/?token=..... //Authorization: Bearer {AccessToken} // - - // Not sure how to do this in httpclient - var http = new HttpClient (); - http.DefaultRequestHeaders.ExpectContinue = false; //Disable expect-100 to improve latency - - http.DefaultRequestHeaders.TryAddWithoutValidation ("X-WNS-Type", string.Format ("wns/{0}", notification.Type.ToString().ToLower ())); - - if(!http.DefaultRequestHeaders.Contains("Authorization")) //prevent double values - http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "Bearer " + accessToken); - - if (notification.RequestForStatus.HasValue) - http.DefaultRequestHeaders.TryAddWithoutValidation ("X-WNS-RequestForStatus", notification.RequestForStatus.Value.ToString().ToLower()); - - if (notification.TimeToLive.HasValue) - http.DefaultRequestHeaders.TryAddWithoutValidation ("X-WNS-TTL", notification.TimeToLive.Value.ToString()); //Time to live in seconds - - if (notification.Type == WnsNotificationType.Tile) + + using (var httpRequestMessage = new HttpRequestMessage (HttpMethod.Post, notification.ChannelUri)) { - var winTileNot = notification as WnsTileNotification; - - if (winTileNot != null && winTileNot.CachePolicy.HasValue) - http.DefaultRequestHeaders.Add("X-WNS-Cache-Policy", winTileNot.CachePolicy == WnsNotificationCachePolicyType.Cache ? "cache" : "no-cache"); - - if (winTileNot != null && !string.IsNullOrEmpty(winTileNot.NotificationTag)) - http.DefaultRequestHeaders.Add("X-WNS-Tag", winTileNot.NotificationTag); // TILE only + httpRequestMessage.Headers.TryAddWithoutValidation ("X-WNS-Type", string.Format ("wns/{0}", notification.Type.ToString ().ToLower ())); + + if(!httpRequestMessage.Headers.Contains ("Authorization")) //prevent double values + httpRequestMessage.Headers.TryAddWithoutValidation ("Authorization", "Bearer " + accessToken); + + if (notification.RequestForStatus.HasValue) + httpRequestMessage.Headers.TryAddWithoutValidation ("X-WNS-RequestForStatus", notification.RequestForStatus.Value.ToString ().ToLower ()); + + if (notification.TimeToLive.HasValue) + httpRequestMessage.Headers.TryAddWithoutValidation ("X-WNS-TTL", notification.TimeToLive.Value.ToString ()); //Time to live in seconds + + if (notification.Type == WnsNotificationType.Tile) + { + var winTileNot = notification as WnsTileNotification; + + if (winTileNot != null && winTileNot.CachePolicy.HasValue) + httpRequestMessage.Headers.Add ("X-WNS-Cache-Policy", winTileNot.CachePolicy == WnsNotificationCachePolicyType.Cache ? "cache" : "no-cache"); + + if (winTileNot != null && !string.IsNullOrEmpty (winTileNot.NotificationTag)) + httpRequestMessage.Headers.Add ("X-WNS-Tag", winTileNot.NotificationTag); // TILE only + } + else if (notification.Type == WnsNotificationType.Badge) + { + var winTileBadge = notification as WnsBadgeNotification; + + if (winTileBadge != null && winTileBadge.CachePolicy.HasValue) + httpRequestMessage.Headers.Add ("X-WNS-Cache-Policy", winTileBadge.CachePolicy == WnsNotificationCachePolicyType.Cache ? "cache" : "no-cache"); + } + else if (notification.Type == WnsNotificationType.Toast) + { + httpRequestMessage.Headers.TryAddWithoutValidation ("X-WindowsPhone-Target", notification.Type.ToString ().ToLower ()); + } + + HttpContent content = null; + + if (notification.Type == WnsNotificationType.Raw) + { + content = new StreamContent (new MemoryStream (((WnsRawNotification)notification).RawData)); + } + else + { + content = new StringContent ( + notification.Payload.ToString (), // Get XML payload + Encoding.UTF8, + "text/xml"); + } + + using (var result = await http.SendAsync (httpRequestMessage)) + { + var status = ParseStatus (result, notification); + + //RESPONSE HEADERS + // X-WNS-Debug-Trace string + // X-WNS-DeviceConnectionStatus connected | disconnected | tempdisconnected (if RequestForStatus was set to true) + // X-WNS-Error-Description string + // X-WNS-Msg-ID string (max 16 char) + // X-WNS-NotificationStatus received | dropped | channelthrottled + // + + // 200 OK + // 400 One or more headers were specified incorrectly or conflict with another header. + // 401 The cloud service did not present a valid authentication ticket. The OAuth ticket may be invalid. + // 403 The cloud service is not authorized to send a notification to this URI even though they are authenticated. + // 404 The channel URI is not valid or is not recognized by WNS. - Raise Expiry + // 405 Invalid Method - never will get + // 406 The cloud service exceeded its throttle limit. + // 410 The channel expired. - Raise Expiry + // 413 The notification payload exceeds the 5000 byte size limit. + // 500 An internal failure caused notification delivery to fail. + // 503 The server is currently unavailable. + + // OK, everything worked! + if (status.HttpStatus == HttpStatusCode.OK + && status.NotificationStatus == WnsNotificationSendStatus.Received) { + return; + } + + //401 + if (status.HttpStatus == HttpStatusCode.Unauthorized) { + AccessTokenManager.InvalidateAccessToken (accessToken); + throw new RetryAfterException (notification, "Access token expired", DateTime.UtcNow.AddSeconds (5)); + } + + //404 or 410 + if (status.HttpStatus == HttpStatusCode.NotFound || status.HttpStatus == HttpStatusCode.Gone) { + throw new DeviceSubscriptionExpiredException (notification) { + OldSubscriptionId = notification.ChannelUri, + ExpiredAt = DateTime.UtcNow + }; + } + + // Any other error + throw new WnsNotificationException (status); + } } - else if (notification.Type == WnsNotificationType.Badge) - { - var winTileBadge = notification as WnsBadgeNotification; - - if (winTileBadge != null && winTileBadge.CachePolicy.HasValue) - http.DefaultRequestHeaders.Add("X-WNS-Cache-Policy", winTileBadge.CachePolicy == WnsNotificationCachePolicyType.Cache ? "cache" : "no-cache"); - } - else if(notification.Type == WnsNotificationType.Toast) - { - http.DefaultRequestHeaders.TryAddWithoutValidation("X-WindowsPhone-Target", notification.Type.ToString().ToLower()); - } - - HttpContent content = null; - - if (notification.Type == WnsNotificationType.Raw) - { - content = new StreamContent(new MemoryStream(((WnsRawNotification)notification).RawData)); - } - else - { - content = new StringContent( - notification.Payload.ToString(), // Get XML payload - Encoding.UTF8, - "text/xml"); - } - - var result = await http.PostAsync (notification.ChannelUri, content); - - var status = ParseStatus (result, notification); - - //RESPONSE HEADERS - // X-WNS-Debug-Trace string - // X-WNS-DeviceConnectionStatus connected | disconnected | tempdisconnected (if RequestForStatus was set to true) - // X-WNS-Error-Description string - // X-WNS-Msg-ID string (max 16 char) - // X-WNS-NotificationStatus received | dropped | channelthrottled - // - - // 200 OK - // 400 One or more headers were specified incorrectly or conflict with another header. - // 401 The cloud service did not present a valid authentication ticket. The OAuth ticket may be invalid. - // 403 The cloud service is not authorized to send a notification to this URI even though they are authenticated. - // 404 The channel URI is not valid or is not recognized by WNS. - Raise Expiry - // 405 Invalid Method - never will get - // 406 The cloud service exceeded its throttle limit. - // 410 The channel expired. - Raise Expiry - // 413 The notification payload exceeds the 5000 byte size limit. - // 500 An internal failure caused notification delivery to fail. - // 503 The server is currently unavailable. - - // OK, everything worked! - if (status.HttpStatus == HttpStatusCode.OK - && status.NotificationStatus == WnsNotificationSendStatus.Received) { - return; - } - - //401 - if (status.HttpStatus == HttpStatusCode.Unauthorized) { - AccessTokenManager.InvalidateAccessToken (accessToken); - throw new RetryAfterException (notification, "Access token expired", DateTime.UtcNow.AddSeconds (5)); - } - - //404 or 410 - if (status.HttpStatus == HttpStatusCode.NotFound || status.HttpStatus == HttpStatusCode.Gone) { - throw new DeviceSubscriptionExpiredException (notification) { - OldSubscriptionId = notification.ChannelUri, - ExpiredAt = DateTime.UtcNow - }; - } - - - // Any other error - throw new WnsNotificationException (status); } - WnsNotificationStatus ParseStatus(HttpResponseMessage resp, WnsNotification notification) + WnsNotificationStatus ParseStatus (HttpResponseMessage resp, WnsNotification notification) { - var result = new WnsNotificationStatus(); + var result = new WnsNotificationStatus (); result.Notification = notification; result.HttpStatus = resp.StatusCode; @@ -175,16 +177,16 @@ WnsNotificationStatus ParseStatus(HttpResponseMessage resp, WnsNotification noti result.ErrorDescription = wnsErrorDescription; result.MessageId = wnsMsgId; - if (wnsNotificationStatus.Equals("received", StringComparison.InvariantCultureIgnoreCase)) + if (wnsNotificationStatus.Equals ("received", StringComparison.InvariantCultureIgnoreCase)) result.NotificationStatus = WnsNotificationSendStatus.Received; - else if (wnsNotificationStatus.Equals("dropped", StringComparison.InvariantCultureIgnoreCase)) + else if (wnsNotificationStatus.Equals ("dropped", StringComparison.InvariantCultureIgnoreCase)) result.NotificationStatus = WnsNotificationSendStatus.Dropped; else result.NotificationStatus = WnsNotificationSendStatus.ChannelThrottled; - if (wnsDeviceConnectionStatus.Equals("connected", StringComparison.InvariantCultureIgnoreCase)) + if (wnsDeviceConnectionStatus.Equals ("connected", StringComparison.InvariantCultureIgnoreCase)) result.DeviceConnectionStatus = WnsDeviceConnectionStatus.Connected; - else if (wnsDeviceConnectionStatus.Equals("tempdisconnected", StringComparison.InvariantCultureIgnoreCase)) + else if (wnsDeviceConnectionStatus.Equals ("tempdisconnected", StringComparison.InvariantCultureIgnoreCase)) result.DeviceConnectionStatus = WnsDeviceConnectionStatus.TempDisconnected; else result.DeviceConnectionStatus = WnsDeviceConnectionStatus.Disconnected;