Skip to content
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

Use one HttpClient for WNS. #796

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
222 changes: 112 additions & 110 deletions PushSharp.Windows/WnsConnection.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -40,10 +38,15 @@ public WnsServiceBroker (WnsConfiguration configuration) : base (new WnsServiceC

public class WnsServiceConnection : IServiceConnection<WnsNotification>
{
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; }
Expand All @@ -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;
Expand All @@ -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;
Expand Down