Using web sockets and events in MS Bot Framework - c#

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

Related

How to read data from a websocket hosted on Azure API Management using a C# ClientWebSocket?

I have the following architecture piece that I am trying to test.
My goal is to read data published to Azure API Management through the AzurePubSub using a WebSocket client from my C# Console App. Azure PubSub is another element you are seeing on green that exposes a WebSocket endpoint that I am not going to make public for my clients. As part of Azure Api Management, I can expose it to external clients using a subscription key and my organization URL.
Using code from this repo: azure pubsub examples I was able to tap into the Azure PubSub element directly to read and write data from there. Here is my AzurePubSubSubscriber using the above repo
using System;
using System.Threading.Tasks;
using Azure.Messaging.WebPubSub;
using Websocket.Client;
namespace subscriber
{
class Program
{
static async Task Main(string[] args)
{
var connectionString = "Endpoint=https://pubsub.webpubsub.azure.com;AccessKey=myaccesskey;Version=1.0;";
var hub = "myhub";
// Either generate the URL or fetch it from server or fetch a temp one from the portal
var serviceClient = new WebPubSubServiceClient(connectionString, hub);
var url = serviceClient.GetClientAccessUri();
using (var client = new WebsocketClient(url))
{
// Disable the auto disconnect and reconnect because the sample would like the client to stay online even no data comes in
client.ReconnectTimeout = null;
client.MessageReceived.Subscribe(msg => Console.WriteLine($"Message received: {msg}"));
await client.Start();
Console.WriteLine("Connected.");
Console.Read();
}
}
}
}
Here is my AzurePubSubPublisher
using System.Threading.Tasks;
using Azure;
using Azure.Core;
using Azure.Messaging.WebPubSub;
namespace publisher
{
class Program
{
static async Task Main(string[] args)
{
await Method1();
}
public static async Task Method1()
{
var connectionString = $"Endpoint=https://pubsub.webpubsub.azure.com;AccessKey=myaccesskey;Version=1.0;";
var hub = "myhub";
while (true)
{
Console.WriteLine("Write your message");
var message = Console.ReadLine();
if (string.IsNullOrEmpty(message))
{
return;
}
// Either generate the token or fetch it from server or fetch a temp one from the portal
var serviceClient = new WebPubSubServiceClient(connectionString, hub);
await serviceClient.SendToAllAsync(message);
}
}
}
}
Now when I expose the endpoint through the Azure API Management to consume content from my Console C# App is where I am not receiving data and am not sure what I am missing here.
Here is the C# code I used from my Console App to read data from Azure API Management endpoint:
static async Task Main()
{
await StartWebSocketClient4();
Console.ReadLine();
}
static async Task StartWebSocketClient4()
{
try
{
Console.Write("Connecting....");
var cts = new CancellationTokenSource();
var socket = new ClientWebSocket();
string wsUri = "wss://myorganization.com/sub?subscription-key=ac3529b7619a383dc6ce6e618dbc9b7614a66";
await socket.ConnectAsync(new Uri(wsUri), cts.Token);
Console.WriteLine(socket.State);
await Task.Factory.StartNew(
async () =>
{
var rcvBytes = new byte[1024 * 1024];
var rcvBuffer = new ArraySegment<byte>(rcvBytes);
while (true)
{
WebSocketReceiveResult rcvResult = await socket.ReceiveAsync(rcvBuffer, cts.Token);
byte[] msgBytes = rcvBuffer.Skip(rcvBuffer.Offset).Take(rcvResult.Count).ToArray();
string rcvMsg = Encoding.UTF8.GetString(msgBytes);
Console.WriteLine("Received: {0}", rcvMsg);
}
}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}

How to create a GET route acting as a subscription route

I have a .Net 5 Web API and would like to create a GET endpoint (acting as a subscription) sending data every x seconds. I know that there are tools out there, e.g. SignalR, but I would like to know if it is possible to achieve the same result with a simple route. Maybe a stream could help ...
This is my example controller
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
[HttpGet]
public OkResult SendDataEvery5Seconds()
{
return Ok(); // send back an initial response
// send data every 5 seconds
}
}
I don't know if this is possible with C# but I tried to create a working example using Node showing what I want to achieve:
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.writeHead(200, {
'content-type': 'application/x-ndjson'
});
setInterval(() => {
res.write(JSON.stringify(new Date()) + '\n');
}, 5000);
})
app.listen(3000);
running curl -i http://localhost:3000 should write down a date every 5 seconds.
You can accomplish it like this.
Server code:
[HttpGet]
public async Task Get(CancellationToken ct = default)
{
Response.StatusCode = 200;
Response.Headers["Content-Type"] = "application/x-ndjson";
// you can manage headers of the request only before this line
await Response.StartAsync(ct);
// cancellation token is important, or else your server will continue it's work after client has disconnected
while (!ct.IsCancellationRequested)
{
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes("some data here\n"), ct);
await Response.Body.FlushAsync(ct);
// change '5000' with whatever delay you need
await Task.Delay(5000, ct);
}
}
Corresponding client code (c# example):
var client = new HttpClient();
var response = await client.GetStreamAsync("http://localhost:5000/");
using var responseReader = new StreamReader(response);
while (!responseReader.EndOfStream)
{
Console.WriteLine(await responseReader.ReadLineAsync());
}

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)
);
}
}

Bot Framework - Bot initiates the conversation on Skype

I want my bot to display an introductory message when a user begins a new conversation. I've seen this working with bots in Skype where the bot sends a message before the user types anything.
I have got this working using the Bot Framework Channel Emulator with this code in the MessagesController class:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
await this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate)
{
var reply = message.CreateReply("Hello World!");
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
This displays 'Hello World!' at the beginning of a new conversation. No input required. However on Skype this introductory message does not appear. What I am misunderstanding here? I know it is possible.
Skype is throwing different ActivityTypes given the situation:
You will get a contactRelationUpdate after adding the bot in your contacts. Then we you start talking to the bot, there is no special Activity
When you start a conversation group with the bot included, you will get conversationUpdate
So if you want to welcome your user, you should add the contactRelationUpdate activity type in your test, like:
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate || message.Type == ActivityTypes.ContactRelationUpdate)
{
var reply = message.CreateReply("Hello World!");
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
Extract of the content of the message you receive when adding the bot:
Here From is my user and Recipient is bot. You can see that the Action value is add

Adding websockets interface to standalone process

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);
}
}

Categories

Resources