I have an API call using client.GetAsync(url) within a SSIS script task but for some reason its not waiting for response from API and jumping back to the entry point for the script task which is public void Main(). Done some reading and found out that it might result in a deadlock for some reason but tried all the variations I could find to get it to work but with no luck. Something else that I don't understand is the exact same code is running on a webpage and that works perfect and waits for response from the api and continuing the flow.
Script Task entry point
The response here for payload is: ID =5, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
Here if in debug mode and moving the process back to go through the process again I noticed there 2 threads one current executing and the old one with the response I was expecting on the first call but not too sure what this means.
public void Main() {
// TODO: Add your code here
try {
PackageDetails packageInfo = new PackageDetails {
PackageNumber = 1234567891, Env = "Development", UserName = "USER"
};
var payload = API.ListHeadByPackAsync(packageInfo);
//var test = GetResponse();
Dts.TaskResult = (int) ScriptResults.Success;
} catch (Exception ex) {
System.Console.Write(ex.Message);
Dts.TaskResult = (int) ScriptResults.Failure;
}
}
API Call
public static class API {
public static async Task<PackageDetails> ListHeadByPackAsync(PackageDetails package) {
PackageDetails packageInfo = new PackageDetails();
try {
using(var client = new ApiClient(requestUrl, authToken)) {
var response = await client.GetAsync(); //-> not waiting for response
}
} catch (Exception err) {
switch (err.Message) {
//TODO:
}
}
return packageInfo;
}
}
Client
public class ApiClient: IDisposable {
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private readonly string _credentials;
//private const string MediaTypeXml = "application/csv";
public ApiClient(string baseUrl, string authToken, TimeSpan ? timeout = null) {
_baseUrl = baseUrl;
_credentials = Base64Encode(authToken);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task < string > GetAsync() {
EnsureHttpClientCreated();
using(var response = await _httpClient.GetAsync(_baseUrl).ConfigureAwait(continueOnCapturedContext: false))
//-> after executing above line it will go straight to public void Main(), dose not wait for response
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose() {
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient() {
_httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false) {
Timeout = _timeout
};
if (!string.IsNullOrWhiteSpace(_baseUrl)) {
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Add("Authorization", "Basic" + " " + _credentials);
}
private void EnsureHttpClientCreated() {
if (_httpClient == null) {
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CreateHttpClient();
}
}
public static string Base64Encode(string token) {
var tokenBytes = System.Text.Encoding.UTF8.GetBytes(token);
return System.Convert.ToBase64String(tokenBytes);
}
}
Related
I can't use the Twilio SDK in Microsoft Dynamics 365 (Twilio library is not installed in Dynamics and can't include the dll in my plugin registration) so I've had to do a http post using the HttpClient. The call to Twilio happens successfully because Twilio is able to send me an verification email but the breakpoint after PostAsync never gets hit, nor does an exception get caught. I need to capture the output from the PostAsync. What am I doing wrong?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public class TwilioMessageInput
{
public string To { get; set; }
public string Channel { get; set; }
}
public class TwilioMessageOutput
{
public string Message { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
// https://www.twilio.com/docs/verify
// https://www.twilio.com/docs/verify/email
string url = "https://verify.twilio.com/v2/Services/VA********************************/Verifications/";
string authToken = "AC********************************:********************************"; //-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN
string email = "***************#************.com";
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("To", email),
new KeyValuePair<string, string>("Channel", "email")
});
using (var client = new Rest(url))
{
var response = client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
}
}
}
public class Rest : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "twillio-client-v1";
private const string MediaTypeJson = "application/json";
public Rest(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
//_timeout = TimeSpan.FromSeconds(1);
}
private async Task<string> PostAsyncInternal(string url, FormUrlEncodedContent input, string authToken)
{
try
{
EnsureHttpClientCreated();
var byteArray = Encoding.ASCII.GetBytes(authToken);
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
using (var response = await _httpClient.PostAsync(url, input))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<TResult> PostAsync<TResult>(string url, FormUrlEncodedContent input, string authToken) where TResult : class, new()
{
var strResponse = await PostAsyncInternal(url, input, authToken);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
Twilio developer evangelist here.
I'm not a C# or Dynamics developer, so sorry if this doesn't help. When you make the request:
var response = client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
it is an asynchronous request, but you do not seem to be waiting for the asynchronous response at all. Should that be?
var response = await client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
I have multiple threads executing API calls parallelly. Whenever the jwt token expires i want all my threads to wait until the refresh token API is called and returns with valid updated jwt token. I have a singleton class which has a refresh token method which will make the call and update the token. How can I make sure that all my other threads will wait until token fetch is complete?
public class JWTTokenManager
{
private static JWTTokenManager _tokenManager;
private string _token;
private bool _refreshingToken;
public static JWTTokenManager GetManager()
{
if (_tokenManager == null)
_tokenManager = new JWTTokenManager();
return _tokenManager;
}
public void UpdateToken(string token)
{
_token = token;
}
public string GetToken()
{
return _token;
}
public async Task<bool> ValidateRefreshTocken()
{
UserInfo userdata = JsonConvert.DeserializeObject<UserInfo>(GetUserInfo(_token), new Helper.DefaultJsonSetting());
if (!string.IsNullOrWhiteSpace(userdata.Exp) && TokenExpired(long.Parse(userdata.Exp)))
{
_refreshingToken = true;
JWTToken jwtToken = Database.DBService.GetDB().FetchJWTToken();
RefreshToken requestRefresh = new RefreshToken
{
ExpiredTocken = jwtToken.Token,
RefreshTocken = jwtToken.RefreshToken
};
HttpClient httpClient = CloudService.GetCloudService().GetHttpClient();
HttpResponseMessage response = await httpClient.PostAsync($"account/v1/tokenRefresh", new StringContent(JsonConvert.SerializeObject(requestRefresh), Encoding.UTF8, "application/json"));
bool responseStatus = await ParseTokenResponseAsync(response);
_refreshingToken = false;
return responseStatus;
}
else
{
return true;
}
}
private string GetUserInfo(string key)
{
string[] base64Url = key.Split('.');
if (base64Url.Length > 1)
{
string userinfo = base64Url[1];
userinfo = userinfo.Replace(" ", "+");
int mod4 = userinfo.Length % 4;
if (mod4 > 0)
{
userinfo += new string('=', 4 - mod4);
}
var base64EncodedBytes = System.Convert.FromBase64String(userinfo);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
else
{
return "";
}
}
public bool TokenExpired(long unixTimeStamp)
{
DateTime tokenExpiryDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
tokenExpiryDateTime = tokenExpiryDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
DateTime currentDateTime = DateTime.Now;
return DateTime.Compare(tokenExpiryDateTime, currentDateTime) <= 0;
}
public async Task<bool> ParseTokenResponseAsync(HttpResponseMessage httpResponse)
{
if (httpResponse.IsSuccessStatusCode == true)
{
string responseString = await httpResponse.Content.ReadAsStringAsync();
Newtonsoft.Json.Linq.JObject responsedataObject = Newtonsoft.Json.Linq.JObject.Parse(responseString);
string token = responsedataObject["data"]["token"].ToString();
string refreshToken = responsedataObject["data"]["refreshToken"].ToString();
_token = token;
JWTToken updatedToken = new JWTToken()
{
Token = _token,
RefreshToken = refreshToken
};
Database.DBService.GetDB().InsertOrUpdateJWTToken(updatedToken);
return true;
}
else
{
return false;
}
}
}
public class CloudService
{
private const int TIME_OUT = 50;
private const int HTTP_GET = 0;
private const int HTTP_PUT = 1;
private const int HTTP_POST = 2;
private const int HTTP_DELETE = 3;
private static CloudService _serviceInstance;
public static CloudService GetCloudService()
{
if (_serviceInstance == null)
_serviceInstance = new CloudService();
return _serviceInstance;
}
private async Task<HttpResponseMessage> ExecuteHttpTask(int taskType, string url, StringContent content = null)
{
HttpClient httpClient = GetHttpClient();
switch (taskType)
{
case HTTP_GET:
return await httpClient.GetAsync(url);
case HTTP_PUT:
return await httpClient.PutAsync(url, content);
case HTTP_POST:
return await httpClient.PostAsync(url, content);
case HTTP_DELETE:
return await httpClient.DeleteAsync(url);
default:
return null;
}
}
public async Task<Response> HTTPTask(string url, int taskType, StringContent content = null, bool login = false)
{
bool refreshTocken = await JWTTokenManager.GetManager().ValidateRefreshTocken();
Response httpResponse = new Response();
try
{
HttpResponseMessage response = await ExecuteHttpTask(taskType, url, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
httpResponse.status = "error";
else
httpResponse.status = "data";
httpResponse.data = ParseResponseData(httpResponse.status, responseString);
}
catch (Exception e)
{
httpResponse = GenericErrorResponse(e.Message);
}
return httpResponse;
}
public async Task<Response> GetSectionAsync(string id)
{
string url = $"catalog/v2/homepageSections/{id}?order-by=name,asc";
return await HTTPTask(url, Constants.HTTP_GET);
}
public async Task<Response> GetProductAsync(string id)
{
string url = $"catalog/v2/products/{id}";
return await HTTPTask(url, Constants.HTTP_GET);
}
public async Task<Response> GetCourseDetailsAsync(string id)
{
string url = $"catalog/v2/products/{id}/courseDetails";
return await HTTPTask(url, Constants.HTTP_GET);
}
}
Different threads will call methods in ClouService which in turn calls different APIs parallelly and all these go through the HTTPTask method, where the token is validated and if not valid, API is called to get the updated token. How can I make all the APIs(the threads) to wait from the moment when token is invalid and until the API returns valid token?
Based on the comments I have updated both the classes. Please have a look.
public sealed class JWTTokenManager
{
private static readonly JWTTokenManager _tokenManager = new JWTTokenManager();
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private string _token;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static JWTTokenManager()
{
}
private JWTTokenManager()
{
}
public static JWTTokenManager GetManager()
{
return _tokenManager;
}
public void UpdateToken(string token)
{
_token = token;
}
public string GetToken()
{
return _token;
}
public async Task<bool> ValidateRefreshTocken(bool forceRefresh = false)
{
bool validToken;
await _semaphore.WaitAsync();
try
{
UserInfo userdata = JsonConvert.DeserializeObject<UserInfo>(GetUserInfo(_token), new DefaultJsonSetting());
if (forceRefresh || !string.IsNullOrWhiteSpace(userdata.Exp) && TokenExpired(long.Parse(userdata.Exp)))
{
validToken = await RefreshToken();
}
else
{
validToken = true;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
validToken = false;
}
finally
{
_semaphore.Release();
}
return validToken;
}
private async Task<bool> RefreshToken()
{
JWTToken jwtToken = Database.DBService.GetDB().FetchJWTToken();
RefreshToken requestRefresh = new RefreshToken
{
ExpiredTocken = jwtToken.Token,
RefreshTocken = jwtToken.RefreshToken
};
HttpClient httpClient = CloudService.GetCloudService().GetHttpClient();
HttpResponseMessage response = await httpClient.PostAsync($"account/v1/tokenRefresh", new StringContent(JsonConvert.SerializeObject(requestRefresh), Encoding.UTF8, "application/json"));
bool status = await ParseTokenResponseAsync(response);
return status;
}
private string GetUserInfo(string key)
{
string[] base64Url = key.Split('.');
if (base64Url.Length > 1)
{
string userinfo = base64Url[1];
userinfo = userinfo.Replace(" ", "+");
int mod4 = userinfo.Length % 4;
if (mod4 > 0)
{
userinfo += new string('=', 4 - mod4);
}
var base64EncodedBytes = System.Convert.FromBase64String(userinfo);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
else
{
return "";
}
}
public bool TokenExpired(long unixTimeStamp)
{
DateTime tokenExpiryDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
tokenExpiryDateTime = tokenExpiryDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
DateTime currentDateTime = DateTime.Now;
return DateTime.Compare(tokenExpiryDateTime, currentDateTime) <= 0;
}
public async Task<bool> ParseTokenResponseAsync(HttpResponseMessage httpResponse)
{
if (httpResponse.IsSuccessStatusCode == true)
{
string responseString = await httpResponse.Content.ReadAsStringAsync();
Newtonsoft.Json.Linq.JObject responsedataObject = Newtonsoft.Json.Linq.JObject.Parse(responseString);
string token = responsedataObject["data"]["token"].ToString();
string refreshToken = responsedataObject["data"]["refreshToken"].ToString();
_token = token;
JWTToken updatedToken = new JWTToken()
{
Token = _token,
RefreshToken = refreshToken
};
Database.DBService.GetDB().InsertOrUpdateJWTToken(updatedToken);
return true;
}
else
{
return false;
}
}
}
public sealed class CloudService
{
private const int TIME_OUT = 50;
private const int HTTP_GET = 0;
private const int HTTP_PUT = 1;
private const int HTTP_POST = 2;
private const int HTTP_DELETE = 3;
private static readonly CloudService _serviceInstance = new CloudService();
static CloudService()
{
}
private CloudService()
{
}
public static CloudService GetCloudService()
{
return _serviceInstance;
}
public HttpClient GetHttpClient()
{
HttpClient httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(TIME_OUT),
BaseAddress = new Uri($"{AppConst.ServerBaseURL}/"),
};
httpClient.DefaultRequestHeaders.Add("X-Jwt-Token", JWTTokenManager.GetManager().GetToken());
httpClient.DefaultRequestHeaders.Add("tenantId", AppConst.TenanatID);
return httpClient;
}
private async Task<HttpResponseMessage> ExecuteHttpTask(int taskType, string url, StringContent content = null)
{
HttpClient httpClient = GetHttpClient();
switch (taskType)
{
case HTTP_GET:
return await httpClient.GetAsync(url);
case HTTP_PUT:
return await httpClient.PutAsync(url, content);
case HTTP_POST:
return await httpClient.PostAsync(url, content);
case HTTP_DELETE:
return await httpClient.DeleteAsync(url);
default:
return null;
}
}
public async Task<Response> HTTPTask(string url, int taskType, StringContent content = null, bool login = false)
{
Response httpResponse = new Response();
try
{
HttpResponseMessage response = await ExecuteHttpTask(taskType, url, content);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized && !login)
{
bool refreshTocken = await JWTTokenManager.GetManager().ValidateRefreshTocken(true);
if (refreshTocken == true)
{
response = await ExecuteHttpTask(taskType, url, content);
}
else
{
httpResponse = GenericErrorResponse();
return httpResponse;
}
}
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
httpResponse.status = "error";
else
httpResponse.status = "data";
httpResponse.data = ParseResponseData(httpResponse.status, responseString);
}
catch (Exception e)
{
httpResponse = GenericErrorResponse(e.Message);
}
return httpResponse;
}
}
You can use a ManualResetEvent. Let's say you have three threads A, B (calling api calls in parallel) and C (performing token refresh).
private static ManualResetEvent mre = new ManualResetEvent(true); // event is always set except when refreshing the token
Code on thread A,B
mre.WaitOne() // it blocks whenever the event is reset
...
Code on thread C
mre.Reset() // this blocks all the waiting threads
perform the token refresh
mre.Set() // this frees all the waiting threads
You should make JWTTokenManager return a token instead of it needs a token.
If the token is valid, it will return a completed task with the valid token, if it's not, it will return a task that will complete when the token is retrieved. The same task can be awaited by multiple concurrent threads.
public class JWTTokenManager
{
private Task<string> tokenTask;
private readonly object sync = new object();
public Task<string> GetTokenAsync()
{
lock (sync)
{
if (tokenTask.IsCompleted && !IsTokenValid(tokenTask.Result))
{
tokenTask = GetNewTokenAsync();
}
return tokenTask;
}
}
}
Lock-free version:
public class JWTTokenManager
{
private Task<string> tokenTask;
private readonly object sync = new object();
public Task<string> GetTokenAsync()
{
var currentTokenTask = Volatile.Read(ref tokenTask);
if (currentTokenTask .IsCompleted && !IsTokenValid(currentTokenTask .Result))
{
currentTokenTask = GetNewTokenAsync();
Volatile.Write(ref tokenTask, currentTokenTask);
}
return currentTokenTask ;
}
}
I'm writing the function of HTTP request for my UWP project. And I accidentally got the data last night ,which shows me that it takes longer to make a HTTP request with HTTP Client on UWP project than the same operation on Android
As for seraching the answer,I found out another similar question:
UWP http client delay in getting response
So,My question is: What make this delay happen ? Just for the diff of application framework or others ?
Here is my HTTP request codes on UWP:
using ServerMonitor.Controls;
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ServerMonitor.Services.RequestServices
{
public class HTTPRequest : BasicRequest, IRequest
{
private const short HTTPPORT = 80;
private const short HTTPSPORT = 443;
private string requestInfo = null;
public static HTTPRequest Instance
{
get
{
return Nested.instance;
}
}
private TransportProtocol httpOrhttps = TransportProtocol.http;
public string Uri { set => uri = value; }
public TransportProtocol ProtocolType { set => httpOrhttps = value; }
public string RequestInfo { get => requestInfo; }
private HTTPRequest() { }
private async Task<bool> HttpRequest(string uri)
{
Stopwatch stopwatch = new Stopwatch();
try
{
HttpClientHandler handler = new HttpClientHandler
{
AllowAutoRedirect = true
};
using (HttpClient client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Referrer = new Uri(uri);
client.Timeout = TimeSpan.FromSeconds(OverTime);
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(OverTime));
HttpResponseMessage message = null;
stopwatch.Start();
Task queryTask = Task.Run(async() =>
{
message = await client.GetAsync(uri, cts.Token);
stopwatch.Stop();
});
var ranTask = Task.WaitAny(queryTask, Task.Delay(OverTime));
if (0 != ranTask)
{
stopwatch.Stop();
if (!cts.IsCancellationRequested) {
cts.Cancel();
}
TimeCost = OverTime;
Status = "1002";
Exception e = new TaskCanceledException("Overtime");
requestInfo = e.ToString();
ErrorException = e;
return false;
}
if (null == message)
{
Status = "500";
TimeCost = (int)(OverTime * 1.5);
requestInfo = "Failed Request : Response Message Is Null";
}
else {
TimeCost = (int)stopwatch.ElapsedMilliseconds;
Status = ((int)Enum.Parse(typeof(System.Net.HttpStatusCode), message.StatusCode.ToString())).ToString();
requestInfo = string.Format("{0} in {1}ms",message.StatusCode, stopwatch.ElapsedMilliseconds);
Debug.WriteLine(requestInfo);
}
}
await Task.CompletedTask;
return true;
}
catch (TaskCanceledException e)
{
Debug.WriteLine("请求超时");
DBHelper.InsertErrorLog(e);
TimeCost = OverTime;
Status = "1002";
ErrorException = e;
requestInfo = "Request OverTime !";
return false;
}
catch (OperationCanceledException e)
{
Debug.WriteLine("请求失败" + e.Message);
DBHelper.InsertErrorLog(e);
TimeCost = (int)(OverTime*1.5);
ErrorException = e;
Status = "1002";
requestInfo = e.Message;
return false;
}
catch (Exception e)
{
Debug.WriteLine("请求失败" + e.Message);
DBHelper.InsertErrorLog(e);
TimeCost = (int)(OverTime * 1.5);
ErrorException = e;
Status = "1001";
requestInfo = e.Message;
return false;
}
}
public async Task<bool> MakeRequest()
{
bool result = false;
switch (httpOrhttps)
{
case TransportProtocol.http:
result = await HttpRequest(uri);
return result;
default:
return result;
}
}
private class Nested
{
static Nested()
{
}
internal static readonly HTTPRequest instance = new HTTPRequest();
}
}
public enum TransportProtocol
{
http,
https
};
}
And followed are some of my screen shots:
Request from uwp application:
Request on Android application:
My UWP environment:
Windows 10.16299 SDK + Framework 4.6 + Visual Studio 2017
Is it possible to trigger the below code by using a trigger URL?
As opposed to triggering by visiting the URL in the browser.
var context = listener.GetContext();
Something like this?
var triggerURL = "http://www.google.ie/";
var request = (HttpWebRequest)WebRequest.Create(triggerURL);
Or is it possible to use a do while loop? I.E do create trigger while get context
Instead of using listener.GetContext(), I was able to satisfy my requirement by using listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener) and listener.EndGetContext(result), utilising the Asynchronous call, GetAsync.
public static string RunServerAsync(Action<string> triggerPost)
{
var triggerURL = "";
CommonCode(ref triggerURL);
if (listener.IsListening)
{
triggerPost(triggerURL);
}
while (listener.IsListening)
{
var context = listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
context.AsyncWaitHandle.WaitOne(20000, true); //Stop listening after 20 seconds (20 * 1000).
listener.Close();
}
return plateString;
}
private static async void TriggerURL(string url)
{
var r = await DownloadPage(url);
}
static async Task<string> DownloadPage(string url)
{
using (var client = new HttpClient())
{
using (var r = await client.GetAsync(new Uri(url)))
{
if (r.IsSuccessStatusCode)
{
string result = await r.Content.ReadAsStringAsync();
return result;
}
else
{
return r.StatusCode.ToString();
}
}
}
}
private static void ListenerCallback(IAsyncResult result)
{
try
{
HttpListener listener = (HttpListener)result.AsyncState;
// Use EndGetContext to complete the asynchronous operation.
HttpListenerContext context = listener.EndGetContext(result);
if (context != null)
{
plateString = ProcessRequest(context);
}
else
{
plateString = "No response received!";
}
}
catch (Exception ex)
{
NLogManager.LogException(ex);
}
}
I'm trying to create a quite simple notifications system (don't want to use SignalIR or something else). I have the following testing code:
Client side:
var source = new EventSource('/notifications.axd');
source.onopen = function () {
Console.log("Connection open");
};
source.onerror = function () {
Console.log("Connection error");
};
source.onmessage = function (event) {
Console.log("Message: " + event.data);
};
Server side:
public class NotificationMessage {
public NotificationMessage() {
Id = Guid.NewGuid().ToString();
}
public string Id { get; private set; }
}
public class NotificationsHandler : HttpTaskAsyncHandler {
private const string CONTENT_TYPE = "text/event-stream";
private sealed class NotificationItem {
public ConcurrentQueue<NotificationMessage> Messages;
public CancellationTokenSource CancellationTokenSource;
}
private static readonly ConcurrentDictionary<string, NotificationItem> _tasks =
new ConcurrentDictionary<string, NotificationItem>();
public static void Notify(string hostId, string userId, NotificationMessage message) {
NotificationItem item;
if (!_tasks.TryGetValue(string.Format("{0}|{1}", hostId, userId), out item)) {
return;
}
var tokenSource = item.CancellationTokenSource;
item.Messages.Enqueue(message);
item.CancellationTokenSource = new CancellationTokenSource();
tokenSource.Cancel();
}
public override async Task ProcessRequestAsync(HttpContext context) {
HttpRequest request = context.Request;
NotificationItem item = _tasks.GetOrAdd(
string.Format("{0}|{1}", request.Url.Host, CsSession.Data.CurrentUser.Id),
k => new NotificationItem {
Messages = new ConcurrentQueue<NotificationMessage>(),
CancellationTokenSource = new CancellationTokenSource()
}
);
HttpResponse response = context.Response;
response.ContentType = CONTENT_TYPE;
response.CacheControl = "no-cache";
response.ContentEncoding = Encoding.UTF8;
response.AppendHeader("connection", "keep-alive");
response.BufferOutput = false;
bool supportsAsyncFlush = response.SupportsAsyncFlush;
bool shouldPing = true;
while (response.IsClientConnected) {
try {
NotificationMessage message = null;
if ((!item.Messages.IsEmpty && item.Messages.TryDequeue(out message)) || shouldPing) {
response.Write(string.Format("data:{0}\n\n", message == null ? "{}" : JsonMapper.Serialize(message)));
if (supportsAsyncFlush) {
await Task.Factory.FromAsync(response.BeginFlush, response.EndFlush, null);
} else {
response.Flush();
}
}
} catch (Exception) {
break;
}
var delay = Task.Delay(15000, item.CancellationTokenSource.Token);
await delay;
shouldPing = delay.Status == TaskStatus.RanToCompletion;
}
}
}
The problem is: the above doesn't works. I have two issues:
1) When the client connects, I receive an empty packet (that's ok). Then, if I don't enqueue any messages, after awaiting the Task.Delay, the loop tries to write an empty message again, but I don't know where. The response.Write line never returns (and nothing is being received on the client).
2) If I write to the queue, for some reason the connection is dropped. If I put a breakpoint on the line after the await delay, that line is never executed (while my logic says otherwise :) ). If I cancel the token, the delay task should quit, but it seems it is aborting the whole handler??