Adding websockets interface to standalone process - c#

If I have a standalone process that runs continuously, is there a good method of adding an asp.net websocket interface to that such that I can send messages to interact with this process and also broadcast status updates? Messages can be quite large and/or frequent.
This process doesn't need to be scheduled as it will be running as soon as the application starts.
I am asking because initially I thought I would just run an asp.net application that creates a thread on startup (in Startup.Configure I think?) but I've been reading that asp.net isn't designed for having background threads. Using Windows Services is brought up but I am developing for Linux and OSX.
This is sort of what I'm trying to achieve:
public class Startup
{
BackgroundServer server;
public void Configure(IApplicationBuilder app)
{
// start server in another thread
Task task = new Task( () => server.run(); );
task.Start();
app.Map("/ws", webSocketsApp =>
{
// need this to enable websocket middleware!
webSocketsApp.UseWebSockets(new WebSocketOptions() {ReplaceFeature = true, });
webSocketsApp.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Task.WhenAll([]{ProcessMessages(webSocket), ProcessSends(websocket)});
return;
}
await next();
});
});
}
private async Task ProcessMessages(WebSocket webSocket)
{
byte[] buffer = new byte[1024 * 4];
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await server.sendMessage(websocket, message);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
private async Task ProcessSends(WebSocket webSocket)
{
while (!webSocket.isStateTerminal)
{
var message = await server.getMessage();
await webSocket.SendAsync(new ArraySegment<byte>(message), Text, true, CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
}

Related

Blazor Server Initiating Task Halts Application

I have a Blazor Server app that calls a number of APIs. Everything works fine, but I am trying to wrap these calls in Tasks. As soon as my code gets to the call, everything just stops. I am sure I am doing something stupid, but no end of Googling is finding me the solution. The call comes from a Syncfusion Grid when selecting a row. Here is my minimum reproducable code:
public static IEnumerable<Quotation> customerQuotations = Array.Empty<Quotation>();
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
GetCustomerQuotes(args.Data.customerId);
}
static async void GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
task.Wait();
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
private static async Task<string> GetJsonString(string url)
{
var TCS = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
HttpResponseMessage response = await myClient.GetAsync(url);
string streamResponse = await response.Content.ReadAsStringAsync();
TCS.SetResult(streamResponse);
return await TCS.Task;
}
If I do this how I am doing all of my other calls, i.e. not using a Task, it works fine, so I know it's not a silly error, it's something I am missing in my Task call.
Thanks in anticipation of any help offered.
The main problem, is Task.Wait(). That can deadlock.
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
//GetCustomerQuotes(args.Data.customerId);
await GetCustomerQuotes(args.Data.customerId);
}
//static async void GetCustomerQuotes(int customerId)
async Task GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
// task.Wait();
await task;
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
and of course
var task = GetJsonString(url);
await task;
... (task.Result)
can (should) become
string result = await GetJsonString(url);
... (result)
And when you don't need the response object (for status code etc) then all this can be done in 1 line:
customerQuotations = await myClient.GetFromJsonAsync<Quotation[]>(url);
It looks like you are overcomplicating the async coding in the API call. Why do you need to construct a TaskCompletionSource? You may have reasons, but they are not evident in the code in your question.
Why not something like this:
public async Task CustomerRowSelectHandler(...)
{
await GetCustomerQuotes(...);
}
private async ValueTask GetCustomerQuotes(...)
{
string url = string.Format(#"....");
var http = new HttpClient(...);
HttpResponseMessage response = await http.GetAsync(url);
if (response.IsSuccessStatusCode)
customerQuotations = await response.Content.ReadFromJsonAsync<IEnumerable<Quotation>>() ?? Enumerable.Empty<Quotation>(); ;
// handle errors
}
Or even this, but you loose the error trapping.
customerQuotations = await http.GetFromJsonAsync<IEnumerable<Quotation>>(url);
You should also consider using the IHttpClientFactory to manage http instances.

using service bus messageReceiver CompleteAsync

I have an azure function that uses a service bus trigger:
public async Task Run(
[ServiceBusTrigger()] string message,
[DurableClient] IDurableOrchestrationClient starter)
{
var response = JsonConvert.DeserializeObject<Response>(message);
await starter.StartNewAsync(nameof(OrchestratorFunction), response);
}
I need to update this to:
public async Task Run(
[ServiceBusTrigger()] Message message, MessageReceiver messageReceiver,
[DurableClient] IDurableOrchestrationClient starter)
{
var response = JsonConvert.DeserializeObject<Response>(Encoding.UTF8.GetString(message.Body));
var res = starter.StartNewAsync(nameof(OrchestratorFunction), response);
await messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
await res (what do I update this line to?)
}
You can use start the orchestration with Await
# here you can get the Instance ID.
var res = await starter.StartNewAsync(nameof(OrchestratorFunction), response);
# so you can skip this part(You don't want to await the Instance ID )
await res (what do I update this line too?)
I have modified the code at the end which resembles completing the Async operation started in the Orchestrator function.
public async Task Run(
[ServiceBusTrigger()] Message message, MessageReceiver messageReceiver,
[DurableClient] IDurableOrchestrationClient starter)
{
var response = JsonConvert.DeserializeObject<Response>(Encoding.UTF8.GetString(message.Body));
# here you can get the Instance ID.
var res = await starter.StartNewAsync(nameof(OrchestratorFunction), response);
# It Completes the message and it will not receive again.
await messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
# This can be done only if the MessageReceiver is created in ReceiveMode.PeekLock mode (which is default).
await messageReceiver.CloseAsync();
# skip this part it won't required because while calling the StartNewAsync we call the await.
//await res (what do I update this line too?)
}
If you want to get the status of your orchestrator function you can use the await starter.GetStatusAsync(< orchestration instanceId>);. After the asynchronous operation of the orchestration client which means after the code of await starter.StartNewAsync().
var orchestrationStatus = await starter.GetStatusAsync(<orchestration instanceId>);
var status = orchestrationStatus.RuntimeStatus.ToString().ToUpper();
while (status == "PENDING" || status == "RUNNING")
{
await Task.Delay(1000);
orchestrationStatus = await starter.GetStatusAsync(< orchestration instanceId>);
status = orchestrationStatus.RuntimeStatus.ToString().ToUpper();
}
Refer here for more information

Polly's Policy.TimeoutAsync does not work with PolicyWrap in an async context

This is a FULLY working example (Copy/paste it and play around, just get the Polly Nuget)
I have the following Console app code which makes a POST request to an HTTP client sandbox on 'http://ptsv2.com/t/v98pb-1521637251/post' (you can visit this link "http://ptsv2.com/t/v98pb-1521637251" to see the configuration or make yourself a "toilet"):
class Program
{
private static readonly HttpClient _httpClient = new HttpClient()
; //WHY? BECAUSE - https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
static void Main(string[] args)
{
_httpClient.BaseAddress = new Uri(#"http://ptsv2.com/t/v98pb-1521637251/post");
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
var result = ExecuteAsync("Test").Result;
Console.WriteLine(result);
Console.ReadLine();
}
private static async Task<string> ExecuteAsync(string request)
{
var response = await Policies.PolicyWrap.ExecuteAsync(async () => await _httpClient.PostAsync("", new StringContent(request)).ConfigureAwait(false));
if (!response.IsSuccessStatusCode)
return "Unsuccessful";
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
The Http client waits for 4 seconds, then returns the response.
I have set up my Policies like this (timeout policy is set up to wait for 1 second for the response):
public static class Policies
{
public static TimeoutPolicy<HttpResponseMessage> TimeoutPolicy
{
get
{
return Policy.TimeoutAsync<HttpResponseMessage>(1, onTimeoutAsync: (context, timeSpan, task) =>
{
Console.WriteLine("Timeout delegate fired after " + timeSpan.TotalMilliseconds);
return Task.CompletedTask;
});
}
}
public static RetryPolicy<HttpResponseMessage> RetryPolicy
{
get
{
return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<TimeoutRejectedException>()
.RetryAsync(3, onRetryAsync: (delegateResult, i) =>
{
Console.WriteLine("Retry delegate fired for time No. " + i);
return Task.CompletedTask;
});
}
}
public static FallbackPolicy<HttpResponseMessage> FallbackPolicy
{
get
{
return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<TimeoutRejectedException>()
.FallbackAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError), onFallbackAsync: (delegateResult, context) =>
{
Console.WriteLine("Fallback delegate fired");
return Task.CompletedTask;
});
}
}
public static PolicyWrap<HttpResponseMessage> PolicyWrap
{
get
{
return Policy.WrapAsync(FallbackPolicy, RetryPolicy, TimeoutPolicy);
}
}
}
However, onTimeoutAsync delegate is not being hit at all and the console prints out the following:
Retry delegate fired for time No. 1 //Hit after 4 seconds
Retry delegate fired for time No. 2 //Hit after 4 more seconds
Retry delegate fired for time No. 3 //Hit after 4 more seconds
Fallback delegate fired
Unsuccessful
It is included in the PolicyWrap and is Async and I do not know why it is not being hit.
Any information is highly appreciated.
Polly Timeout policy with the default TimeoutStrategy.Optimistic operates by timing-out CancellationToken, so the delegates you execute must respond to co-operative cancellation. See the Polly Timeout wiki for more detail.
Changing your execution line to the following should make the timeout work:
var response = await Policies.PolicyWrap.ExecuteAsync(
async ct => await _httpClient.PostAsync(/* uri */, new StringContent(request), ct).ConfigureAwait(false),
CancellationToken.None // CancellationToken.None here indicates you have no independent cancellation control you wish to add to the cancellation provided by TimeoutPolicy. You can also pass in your own independent CancellationToken.
);
Polly async executions by default do not continue on captured synchronization context (they execute with .ConfigureAwait(false)), so this can also be shortened to:
var response = await Policies.PolicyWrap.ExecuteAsync(
ct => _httpClient.PostAsync(/* uri */, new StringContent(request), ct),
CancellationToken.None
);

Owin self hosting - limiting maximum time for request

How to efficiently limit request length timeout on server side ? I'm using Microsoft.Owin.Host.HttpListener and there are cases when (due to call to external service) serving request takes ridiculous amount of time. This is not a problem - but web server should give up sooner than - well never (I did some tests, but after 5 minutes I stopped it).
Is there a way how to limit time for serving single request (similar to <httpRuntime maxRequestLength="..." /> in IIS ecosystem) ?
Sample controller code:
public async Task<HttpResponseMessage> Get() {
// ... calls to 3pty services here
await Task.Delay(TimeSpan.FromMinutes(5));
}
Starting web server:
WebApp.Start(this.listeningAddress, new Action<IAppBuilder>(this.Build));
Note: I've read about limiting http listener, but that just limits incoming request properties, it doesn't cancel request that is slow due to slow server processing:
var listener = appBuilder.Properties[typeof(OwinHttpListener).FullName] as OwinHttpListener;
var timeoutManager = listener.Listener.TimeoutManager;
timeoutManager.DrainEntityBody = TimeSpan.FromSeconds(20);
timeoutManager.EntityBody = TimeSpan.FromSeconds(20);
timeoutManager.HeaderWait = TimeSpan.FromSeconds(20);
timeoutManager.IdleConnection = TimeSpan.FromSeconds(20);
timeoutManager.RequestQueue = TimeSpan.FromSeconds(20);
Related:
https://github.com/aspnet/AspNetKatana/issues/152
Conceptually "older" web server solutions - i.e. IIS are using one-thread-per-request separation and ThreadAbortException to kill slow requests. Owin is using different philosophy - i.e. it fires new task per request and forcibly cancelling task is best avoided. There are two sides of this problem:
shus client away if it takes too long
cancel server processing if it takes too long
Both can be achieved using middleware component. There also is a cancellation token provided directly by owin infrastructure for cases when client disconnects (context.Request.CallCancelled where context is IOwinContext)
If you're interested only in cancelling server flow ASAP when it takes to long, I'd recommend something like
public class MyMiddlewareClass : OwinMiddleware
{
// 5 secs is ok for testing, you might want to increase this
const int WAIT_MAX_MS = 5000;
public MyMiddlewareClass(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(
context.Request.CallCancelled))
{
source.CancelAfter(WAIT_MAX_MS);
// combined "client disconnected" and "it takes too long" token
context.Set("RequestTerminated", source.Token);
await Next.Invoke(context);
}
}
}
And then in controller
public async Task<string> Get()
{
var context = this.Request.GetOwinContext();
var token = context.Get<CancellationToken>("RequestTerminated");
// simulate long async call
await Task.Delay(10000, token);
token.ThrowIfCancellationRequested();
return "Hello !";
}
Shusing the client away is more complex. The middleware will look like this:
public static async Task ShutDownClientWhenItTakesTooLong(IOwinContext context,
CancellationToken timeoutToken)
{
await Task.Delay(WAIT_MAX_MS, timeoutToken);
if (timeoutToken.IsCancellationRequested)
{
return;
}
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
}
public async Task ExecuteMainRequest(IOwinContext context,
CancellationTokenSource timeoutSource, Task timeoutTask)
{
try
{
await Next.Invoke(context);
}
finally
{
timeoutSource.Cancel();
await timeoutTask;
}
}
public override async Task Invoke(IOwinContext context)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(
context.Request.CallCancelled))
using (var timeoutSource = new CancellationTokenSource())
{
source.CancelAfter(WAIT_MAX_MS);
context.Set("RequestTerminated", source.Token);
var timeoutTask = ShutDownClientWhenItTakesTooLong(context, timeoutSource.Token);
await Task.WhenAny(
timeoutTask,
ExecuteMainRequest(context, timeoutSource, timeoutTask)
);
}
}

Using web sockets and events in MS Bot Framework

I am trying to communicate with echo service on a server using web sockets in my bot. I am using WebSocketSharp assembly to create web socket connection. I want to echo back whatever user types in the bot but, it never fires "ws.OnMessage" event and I get back no response. I tested the connection on the console application and every thing works fine there. Please suggest what I am doing wrong here.
Following is my MessageController
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new HumanCollaboratorDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Following is my HumanCollaboratorDialog class
[Serializable]
public class HumanCollaboratorDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
using (var ws = new WebSocket("ws://Some IP addrress:8080/human-collaborator/echo"))
{
ws.OnMessage += async (sender, e) =>
{
try
{
await context.PostAsync(e.Data);
}
catch (Exception ex)
{
await context.PostAsync($"Exception: {ex.Message}");
}
};
ws.ConnectAsync();
var msg = message.Text;
ws.Send(msg);
}
context.Wait(this.MessageReceivedAsync);
}
}
The "MessageReceivedAsync" is not the correct place to create a websocket. WebSockets in the bot framework are used for receiving messages in a Direct Line connection scenario. A StreamUrl obtained from a call to StartConversationAsync is used to create the web socket:
var token = await new DirectLineClient(dlSecret).Tokens.GenerateTokenForNewConversationAsync();
// Use token to create conversation
var directLineClient = new DirectLineClient(tokenResponse.Token);
var conversation = await directLineClient.Conversations.StartConversationAsync();
using (var webSocketClient = new WebSocket(conversation.StreamUrl))
{
webSocketClient.OnMessage += WebSocketClient_OnMessage;
webSocketClient.Connect();
etc.
Please see here: https://github.com/Microsoft/BotBuilder-Samples/blob/master/CSharp/core-DirectLineWebSockets/DirectLineClient/Program.cs

Categories

Resources