public async Task Integration_KeepAlive() { // TODO: Transform this test into a theory and do multi-message, multi-thread, multi-client, etc. var logger = XUnitLogger.CreateLogger(_outputHelper); var cts = new CancellationTokenSource(); using (var webSocketFeature = new TestWebSocketConnectionFeature()) { // Bot / server setup var botRequestHandler = new Mock <RequestHandler>(); botRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var socket = await webSocketFeature.AcceptAsync().ConfigureAwait(false); var connection = new WebSocketStreamingConnection(socket, logger); var serverTask = connection.ListenAsync(botRequestHandler.Object, cts.Token); // Client / channel setup var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var client = new WebSocketClient(webSocketFeature.Client, "wss://test", clientRequestHandler.Object, logger: logger, closeTimeOut: TimeSpan.FromSeconds(10), keepAlive: TimeSpan.FromMilliseconds(200)); var clientTask = client.ConnectInternalAsync(CancellationToken.None); // Send request bot (server) -> channel (client) const string path = "api/version"; const string botToClientPayload = "Hello human, I'm Bender!"; var request = StreamingRequest.CreatePost(path, new StringContent(botToClientPayload)); var responseFromClient = await connection.SendStreamingRequestAsync(request).ConfigureAwait(false); Assert.Equal(200, responseFromClient.StatusCode); const string clientToBotPayload = "Hello bot, I'm Calculon!"; var clientRequest = StreamingRequest.CreatePost(path, new StringContent(clientToBotPayload)); // Send request bot channel (client) -> (server) var clientToBotResult = await client.SendAsync(clientRequest).ConfigureAwait(false); Assert.Equal(200, clientToBotResult.StatusCode); await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); Assert.True(client.IsConnected); } }
public async Task <Activity> ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext, Activity activity, Action <Activity> tokenRequestHandler = null, Action <Activity> fallbackHandler = null) { if (_streamingTransportClient == null) { // establish websocket connection _streamingTransportClient = new WebSocketClient( EnsureWebSocketUrl(skillManifest.Endpoint.ToString()), new SkillCallingRequestHandler( turnContext, _botTelemetryClient, GetTokenCallback(turnContext, tokenRequestHandler), GetFallbackCallback(turnContext, fallbackHandler), GetHandoffActivityCallback())); } // acquire AAD token MicrosoftAppCredentials.TrustServiceUrl(skillManifest.Endpoint.AbsoluteUri); var token = await serviceClientCredentials.GetTokenAsync(); // put AAD token in the header var headers = new Dictionary <string, string>(); headers.Add("Authorization", $"Bearer {token}"); await _streamingTransportClient.ConnectAsync(headers); // set recipient to the skill var recipientId = activity.Recipient.Id; activity.Recipient.Id = skillManifest.MSAappId; // Serialize the activity and POST to the Skill endpoint var body = new StringContent(JsonConvert.SerializeObject(activity, SerializationSettings.BotSchemaSerializationSettings), Encoding.UTF8, SerializationSettings.ApplicationJson); var request = StreamingRequest.CreatePost(string.Empty, body); // set back recipient id to make things consistent activity.Recipient.Id = recipientId; var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); await _streamingTransportClient.SendAsync(request); stopWatch.Stop(); _botTelemetryClient.TrackEvent( "SkillWebSocketTurnLatency", new Dictionary <string, string> { { "SkillName", skillManifest.Name }, { "SkillEndpoint", skillManifest.Endpoint.ToString() }, }, new Dictionary <string, double> { { "Latency", stopWatch.ElapsedMilliseconds }, }); return(_handoffActivity); }
public void Request_Create_Post_Success() { var r = StreamingRequest.CreatePost(); Assert.AreEqual(StreamingRequest.POST, r.Verb); Assert.IsNull(r.Path); Assert.IsNull(r.Streams); }
public async Task Integration_Interop_LegacyClient() { // TODO: Transform this test into a theory and do multi-message, multi-thread, multi-client, etc. var logger = XUnitLogger.CreateLogger(_outputHelper); using (var webSocketFeature = new TestWebSocketConnectionFeature()) { // Bot / server setup var botRequestHandler = new Mock <RequestHandler>(); botRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var socket = await webSocketFeature.AcceptAsync().ConfigureAwait(false); var connection = new WebSocketStreamingConnection(socket, logger); var serverTask = Task.Run(() => connection.ListenAsync(botRequestHandler.Object)); // Client / channel setup var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); using (var client = new Microsoft.Bot.Streaming.Transport.WebSockets.WebSocketClient("wss://test", clientRequestHandler.Object)) { await client.ConnectInternalAsync(webSocketFeature.Client).ConfigureAwait(false); // Send request bot (server) -> channel (client) const string path = "api/version"; const string botToClientPayload = "Hello human, I'm Bender!"; var request = StreamingRequest.CreatePost(path, new StringContent(botToClientPayload)); var responseFromClient = await connection.SendStreamingRequestAsync(request).ConfigureAwait(false); Assert.Equal(200, responseFromClient.StatusCode); const string clientToBotPayload = "Hello bot, I'm Calculon!"; var clientRequest = StreamingRequest.CreatePost(path, new StringContent(clientToBotPayload)); // Send request bot channel (client) -> (server) var clientToBotResult = await client.SendAsync(clientRequest).ConfigureAwait(false); Assert.Equal(200, clientToBotResult.StatusCode); client.Disconnect(); } await serverTask.ConfigureAwait(false); } }
private void RunStreamingCrashTest(Action <WebSocket, TestWebSocketConnectionFeature.WebSocketChannel, WebSocketClient, CancellationTokenSource, CancellationTokenSource> induceCrash) { var logger = XUnitLogger.CreateLogger(_testOutput); var serverCts = new CancellationTokenSource(); var clientCts = new CancellationTokenSource(); using (var connection = new TestWebSocketConnectionFeature()) { var webSocket = connection.AcceptAsync().Result; var clientWebSocket = connection.Client; var bot = new StreamingTestBot((turnContext, cancellationToken) => Task.CompletedTask); var server = new CloudAdapter(new StreamingTestBotFrameworkAuthentication(), logger); var serverRunning = server.ProcessAsync(CreateWebSocketUpgradeRequest(webSocket), new Mock <HttpResponse>().Object, bot, serverCts.Token); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(h => h.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), It.IsAny <ILogger <RequestHandler> >(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(StreamingResponse.OK())); using (var client = new WebSocketClient("wss://test", clientRequestHandler.Object, logger: logger)) { var clientRunning = client.ConnectInternalAsync(clientWebSocket, clientCts.Token); var activity = new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "test", Text = "hi" }; var content = new StringContent(JsonConvert.SerializeObject(activity), Encoding.UTF8, "application/json"); var response = client.SendAsync(StreamingRequest.CreatePost("/api/messages", content)).Result; Assert.Equal(200, response.StatusCode); induceCrash(webSocket, clientWebSocket, client, serverCts, clientCts); clientRunning.Wait(); Assert.True(clientRunning.IsCompletedSuccessfully); } serverRunning.Wait(); Assert.True(serverRunning.IsCompletedSuccessfully); } }
/// <summary> /// Creates a new conversation on the service. /// Throws <see cref="ArgumentNullException"/> if parameters is null. /// </summary> /// <param name="parameters">The parameters to use when creating the service.</param> /// <param name="cancellationToken">Optional cancellation token.</param> /// <returns>A task that represents the work queued to execute.</returns> public async Task <ConversationResourceResponse> PostConversationAsync(ConversationParameters parameters, CancellationToken cancellationToken = default(CancellationToken)) { if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } var request = StreamingRequest.CreatePost(StreamingChannelPrefix); request.SetBody(parameters); return(await SendRequestAsync <ConversationResourceResponse>(request, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Posts an update to an existing activity. /// Throws <see cref="ArgumentNullException"/> if activity is null. /// </summary> /// <param name="activity">The updated activity.</param> /// <param name="cancellationToken">Optional cancellation token.</param> /// <returns>A task that represents the work queued to execute.</returns> public async Task <ResourceResponse> PostToActivityAsync(Activity activity, CancellationToken cancellationToken = default(CancellationToken)) { if (activity == null) { throw new ArgumentNullException(nameof(activity)); } var route = $"{StreamingChannelPrefix}{activity.Conversation.Id}/activities/{activity.Id}"; var request = StreamingRequest.CreatePost(route); request.SetBody(activity); return(await SendRequestAsync <ResourceResponse>(request, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Converts an <see cref="Activity"/> into a <see cref="StreamingRequest"/> and sends it to the /// channel this StreamingRequestHandler is connected to. /// </summary> /// <param name="activity">The activity to send.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that resolves to a <see cref="ResourceResponse"/>.</returns> public async Task <ResourceResponse> SendActivityAsync(Activity activity, CancellationToken cancellationToken = default) { string requestPath; if (!string.IsNullOrWhiteSpace(activity.ReplyToId) && activity.ReplyToId.Length >= 1) { requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities/{activity.ReplyToId}"; } else { requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities"; } var streamAttachments = UpdateAttachmentStreams(activity); var request = StreamingRequest.CreatePost(requestPath); request.SetBody(activity); if (streamAttachments != null) { foreach (var attachment in streamAttachments) { request.AddStream(attachment); } } try { if (!_serverIsConnected) { throw new Exception("Error while attempting to send: Streaming transport is disconnected."); } var serverResponse = await _server.SendAsync(request, cancellationToken).ConfigureAwait(false); if (serverResponse.StatusCode == (int)HttpStatusCode.OK) { return(serverResponse.ReadBodyAsJson <ResourceResponse>()); } } #pragma warning disable CA1031 // Do not catch general exception types (this should probably be addressed later, but for now we just log the error and continue the execution) catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { _logger.LogError(ex.Message); } return(null); }
public async Task RequestDisassembler_WithJsonStream_Sends() { var sender = new PayloadSender(); var transport = new MockTransportSender(); sender.Connect(transport); var ops = new SendOperations(sender); var request = StreamingRequest.CreatePost("/a/b"); request.AddStream(new StringContent("abc", Encoding.ASCII)); await ops.SendRequestAsync(Guid.NewGuid(), request); Assert.AreEqual(4, transport.Buffers.Count); }
public async Task RequestDisassembler_WithVariableStream_Sends() { var sender = new PayloadSender(); var transport = new MockTransportSender(); sender.Connect(transport); var ops = new SendOperations(sender); var request = StreamingRequest.CreatePost("/a/b"); var stream = new PayloadStream(new PayloadStreamAssembler(null, Guid.NewGuid(), "blah", 100)); stream.Write(new byte[100], 0, 100); request.AddStream(new StreamContent(stream)); await ops.SendRequestAsync(Guid.NewGuid(), request); Assert.AreEqual(5, transport.Buffers.Count); }
/// <summary> /// Updates the conversation history stored on the service. /// Throws <see cref="ArgumentNullException"/> if conversationId or transcript is null. /// </summary> /// <param name="conversationId">The id of the conversation to update.</param> /// <param name="transcript">A transcript of the conversation history, which will replace the history on the service.</param> /// <param name="cancellationToken">Optoinal cancellation token.</param> /// <returns>A task that represents the work queued to execute.</returns> public async Task <ResourceResponse> PostConversationHistoryAsync(string conversationId, Transcript transcript, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrWhiteSpace(conversationId)) { throw new ArgumentNullException(nameof(conversationId)); } if (transcript == null) { throw new ArgumentNullException(nameof(transcript)); } var route = $"{StreamingChannelPrefix}{conversationId}/activities/history"; var request = StreamingRequest.CreatePost(route); request.SetBody(transcript); return(await SendRequestAsync <ResourceResponse>(request, cancellationToken).ConfigureAwait(false)); }
public async Task <bool> ForwardToSkillAsync(ITurnContext turnContext, Activity activity, Action <Activity> tokenRequestHandler = null) { if (_streamingTransportClient == null) { // acquire AAD token MicrosoftAppCredentials.TrustServiceUrl(_skillManifest.Endpoint.AbsoluteUri); var token = await _serviceClientCredentials.GetTokenAsync(); // put AAD token in the header var headers = new Dictionary <string, string>(); headers.Add("Authorization", $"Bearer {token}"); // establish websocket connection _streamingTransportClient = new WebSocketClient( EnsureWebSocketUrl(_skillManifest.Endpoint.ToString()), new SkillCallingRequestHandler( turnContext, _botTelemetryClient, GetTokenCallback(turnContext, tokenRequestHandler), GetHandoffActivityCallback()), headers); await _streamingTransportClient.ConnectAsync(); } // set recipient to the skill var recipientId = activity.Recipient.Id; activity.Recipient.Id = _skillManifest.MSAappId; // Serialize the activity and POST to the Skill endpoint var body = new StringContent(JsonConvert.SerializeObject(activity, SerializationSettings.BotSchemaSerializationSettings), Encoding.UTF8, SerializationSettings.ApplicationJson); var request = StreamingRequest.CreatePost(string.Empty, body); // set back recipient id to make things consistent activity.Recipient.Id = recipientId; await _streamingTransportClient.SendAsync(request); return(endOfConversation); }
private static async Task MessageAsync() { if (_client == null || !_client.IsConnected) { WriteLine("[Program] Client is not connected, connect before sending messages."); } var text = AskUser("[Program] Enter text:"); WriteLine($"[User]: {text}", ConsoleColor.Cyan); if (string.IsNullOrEmpty(_conversationId)) { _conversationId = Guid.NewGuid().ToString(); } var activity = new Schema.Activity() { Id = Guid.NewGuid().ToString(), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = _conversationId }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "Test", Text = text, }; var request = StreamingRequest.CreatePost("/api/messages", new StringContent(JsonConvert.SerializeObject(activity), Encoding.UTF8, "application/json")); var stopwatch = Stopwatch.StartNew(); var response = await _client.SendAsync(request, CancellationToken.None); }
private static void SimulateMultiTurnConversation(int conversationId, Activity[] activities, IStreamingTransportClient client, ILogger logger, SemaphoreSlim throttler = null) { try { foreach (var activity in activities) { var timer = Stopwatch.StartNew(); var content = new StringContent(JsonConvert.SerializeObject(activity), Encoding.UTF8, "application/json"); var response = client.SendAsync(StreamingRequest.CreatePost("/api/messages", content)).Result; logger.LogInformation($"Conversation {conversationId} latency: {timer.ElapsedMilliseconds}. Status code: {response.StatusCode}"); } } finally { if (throttler != null) { throttler.Release(); } } }
/// <summary> /// Converts an <see cref="Activity"/> into a <see cref="StreamingRequest"/> and sends it to the /// channel this StreamingRequestHandler is connected to. /// </summary> /// <param name="activity">The activity to send.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that resolves to a <see cref="ResourceResponse"/>.</returns> public virtual async Task <ResourceResponse> SendActivityAsync(Activity activity, CancellationToken cancellationToken = default) { string requestPath; if (!string.IsNullOrWhiteSpace(activity.ReplyToId) && activity.ReplyToId.Length >= 1) { requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities/{activity.ReplyToId}"; } else { requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities"; } var streamAttachments = UpdateAttachmentStreams(activity); var request = StreamingRequest.CreatePost(requestPath); request.SetBody(activity); if (streamAttachments != null) { foreach (var attachment in streamAttachments) { request.AddStream(attachment); } } var serverResponse = await _innerConnection.SendStreamingRequestAsync(request, cancellationToken).ConfigureAwait(false); if (serverResponse.StatusCode == (int)HttpStatusCode.OK) { return(serverResponse.ReadBodyAsJson <ResourceResponse>()); } else { throw new Exception($"Failed to send request through streaming transport. Status code: {serverResponse.StatusCode}."); } }
public async Task <Schema.ResourceResponse> SendActivityAsync(Schema.Activity activity, List <AttachmentStream> attachmentStreams = null) { SentActivities.Add(activity); var requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities/{activity.Id}"; var request = StreamingRequest.CreatePost(requestPath); request.SetBody(activity); attachmentStreams?.ForEach(a => { var streamContent = new StreamContent(a.ContentStream); streamContent.Headers.TryAddWithoutValidation(HeaderNames.ContentType, a.ContentType); request.AddStream(streamContent); }); var serverResponse = await _adapter.ProcessStreamingActivityAsync(activity, OnTurnAsync, CancellationToken.None).ConfigureAwait(false); if (serverResponse.Status == (int)HttpStatusCode.OK) { return(JsonConvert.DeserializeObject <Schema.ResourceResponse>(serverResponse.Body.ToString())); } throw new Exception("SendActivityAsync failed"); }
/// <summary> /// Sends activities to the conversation. /// </summary> /// <param name="turnContext">The context object for the turn.</param> /// <param name="activities">The activities to send.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the activities are successfully sent, the task result contains /// an array of <see cref="ResourceResponse"/> objects containing the IDs that /// the receiving channel assigned to the activities.</remarks> /// <seealso cref="ITurnContext.OnSendActivities(SendActivitiesHandler)"/> public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken) { if (turnContext == null) { throw new ArgumentNullException(nameof(turnContext)); } if (activities == null) { throw new ArgumentNullException(nameof(activities)); } if (activities.Length == 0) { throw new ArgumentException("Expecting one or more activities, but the array was empty.", nameof(activities)); } var responses = new ResourceResponse[activities.Length]; /* * NOTE: we're using for here (vs. foreach) because we want to simultaneously index into the * activities array to get the activity to process as well as use that index to assign * the response to the responses array and this is the most cost effective way to do that. */ for (var index = 0; index < activities.Length; index++) { var activity = activities[index]; if (string.IsNullOrWhiteSpace(activity.Id)) { activity.Id = Guid.NewGuid().ToString("n"); } var response = default(ResourceResponse); if (activity.Type == ActivityTypesEx.Delay) { // The Activity Schema doesn't have a delay type build in, so it's simulated // here in the Bot. This matches the behavior in the Node connector. var delayMs = (int)activity.Value; await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); // No need to create a response. One will be created below. } // set SemanticAction property of the activity properly EnsureActivitySemanticAction(turnContext, activity); if (activity.Type != ActivityTypes.Trace || (activity.Type == ActivityTypes.Trace && activity.ChannelId == "emulator")) { var requestPath = $"/activities/{activity.Id}"; var request = StreamingRequest.CreatePost(requestPath); // set callerId to empty so it's not sent over the wire activity.CallerId = null; request.SetBody(activity); _botTelemetryClient.TrackTrace($"Sending activity. ReplyToId: {activity.ReplyToId}", Severity.Information, null); var stopWatch = new Diagnostics.Stopwatch(); try { stopWatch.Start(); response = await SendRequestAsync <ResourceResponse>(request).ConfigureAwait(false); stopWatch.Stop(); } catch (Exception ex) { throw new SkillWebSocketCallbackException($"Callback failed. Verb: POST, Path: {requestPath}", ex); } _botTelemetryClient.TrackEvent("SkillWebSocketSendActivityLatency", null, new Dictionary <string, double> { { "Latency", stopWatch.ElapsedMilliseconds }, }); } // If No response is set, then defult to a "simple" response. This can't really be done // above, as there are cases where the ReplyTo/SendTo methods will also return null // (See below) so the check has to happen here. // Note: In addition to the Invoke / Delay / Activity cases, this code also applies // with Skype and Teams with regards to typing events. When sending a typing event in // these _channels they do not return a RequestResponse which causes the bot to blow up. // https://github.com/Microsoft/botbuilder-dotnet/issues/460 // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465 if (response == null) { response = new ResourceResponse(activity.Id ?? string.Empty); } responses[index] = response; } return(responses); }
public async Task Integration_Interop_LegacyClient_MiniLoad(int threadCount, int messageCount) { var logger = XUnitLogger.CreateLogger(_outputHelper); using (var webSocketFeature = new TestWebSocketConnectionFeature()) { var botRequestHandler = new Mock <RequestHandler>(); botRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var socket = await webSocketFeature.AcceptAsync().ConfigureAwait(false); var connection = new WebSocketStreamingConnection(socket, logger); var serverTask = Task.Run(() => connection.ListenAsync(botRequestHandler.Object)); await Task.Delay(TimeSpan.FromSeconds(1)); var clients = new List <Microsoft.Bot.Streaming.Transport.WebSockets.WebSocketClient>(); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); using (var client = new Microsoft.Bot.Streaming.Transport.WebSockets.WebSocketClient( "wss://test", clientRequestHandler.Object)) { await client.ConnectInternalAsync(webSocketFeature.Client).ConfigureAwait(false); clients.Add(client); // Send request bot (server) -> channel (client) const string path = "api/version"; const string botToClientPayload = "Hello human, I'm Bender!"; Func <int, Task> testFlow = async(i) => { var request = StreamingRequest.CreatePost(path, new StringContent(botToClientPayload)); var stopwatch = Stopwatch.StartNew(); var responseFromClient = await connection.SendStreamingRequestAsync(request).ConfigureAwait(false); stopwatch.Stop(); Assert.Equal(200, responseFromClient.StatusCode); logger.LogInformation( $"Server->Client {i} latency: {stopwatch.ElapsedMilliseconds}. Status code: {responseFromClient.StatusCode}"); const string clientToBotPayload = "Hello bot, I'm Calculon!"; var clientRequest = StreamingRequest.CreatePost(path, new StringContent(clientToBotPayload)); stopwatch = Stopwatch.StartNew(); // Send request bot channel (client) -> (server) var clientToBotResult = await client.SendAsync(clientRequest).ConfigureAwait(false); stopwatch.Stop(); Assert.Equal(200, clientToBotResult.StatusCode); logger.LogInformation( $"Client->Server {i} latency: {stopwatch.ElapsedMilliseconds}. Status code: {responseFromClient.StatusCode}"); }; await testFlow(-1).ConfigureAwait(false); var tasks = new List <Task>(); using (var throttler = new SemaphoreSlim(threadCount)) { for (int j = 0; j < messageCount; j++) { await throttler.WaitAsync().ConfigureAwait(false); // using Task.Run(...) to run the lambda in its own parallel // flow on the threadpool tasks.Add( Task.Run(async() => { try { await testFlow(j).ConfigureAwait(false); } finally { throttler.Release(); } })); } await Task.WhenAll(tasks).ConfigureAwait(false); } client.Disconnect(); } await serverTask.ConfigureAwait(false); } }
/// <summary> /// Sends activities to the conversation. /// Throws <see cref="ArgumentNullException"/> on null arguments. /// Throws <see cref="ArgumentException"/> if activities length is zero. /// </summary> /// <param name="turnContext">The context object for the turn.</param> /// <param name="activities">The activities to send.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the activities are successfully sent, the task result contains /// an array of <see cref="ResourceResponse"/> objects containing the IDs that /// the receiving channel assigned to the activities.</remarks> /// <seealso cref="ITurnContext.OnSendActivities(SendActivitiesHandler)"/> public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken = default(CancellationToken)) { if (turnContext == null) { throw new ArgumentNullException(nameof(turnContext)); } if (activities == null) { throw new ArgumentNullException(nameof(activities)); } var responses = new ResourceResponse[activities.Length]; /* * NOTE: we're using for here (vs. foreach) because we want to simultaneously index into the * activities array to get the activity to process as well as use that index to assign * the response to the responses array and this is the most cost effective way to do that. */ for (var index = 0; index < activities.Length; index++) { var activity = activities[index] ?? throw new ArgumentNullException("Found null activity in SendActivitiesAsync."); var response = default(ResourceResponse); _logger.LogInformation($"Sending activity. ReplyToId: {activity.ReplyToId}"); if (activity.Type == ActivityTypesEx.Delay) { // The Activity Schema doesn't have a delay type build in, so it's simulated // here in the Bot. This matches the behavior in the Node connector. var delayMs = (int)activity.Value; await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); // No need to create a response. One will be created below. } else if (activity.Type == ActivityTypesEx.InvokeResponse) { turnContext.TurnState.Add(InvokeReponseKey, activity); // No need to create a response. One will be created below. } else if (activity.Type == ActivityTypes.Trace && activity.ChannelId != "emulator") { // if it is a Trace activity we only send to the channel if it's the emulator. } string requestPath; if (!string.IsNullOrWhiteSpace(activity.ReplyToId) && activity.ReplyToId.Length >= 1) { requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities/{activity.ReplyToId}"; } else { requestPath = $"/v3/conversations/{activity.Conversation?.Id}/activities"; } var streamAttachments = UpdateAttachmentStreams(activity); var request = StreamingRequest.CreatePost(requestPath); request.SetBody(activity); if (streamAttachments != null) { foreach (var attachment in streamAttachments) { request.AddStream(attachment); } } response = await SendRequestAsync <ResourceResponse>(request).ConfigureAwait(false); // If No response is set, then default to a "simple" response. This can't really be done // above, as there are cases where the ReplyTo/SendTo methods will also return null // (See below) so the check has to happen here. // Note: In addition to the Invoke / Delay / Activity cases, this code also applies // with Skype and Teams with regards to typing events. When sending a typing event in // these _channels they do not return a RequestResponse which causes the bot to blow up. // https://github.com/Microsoft/botbuilder-dotnet/issues/460 // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465 if (response == null) { response = new ResourceResponse(activity.Id ?? string.Empty); } responses[index] = response; } return(responses); }