Пример #1
0
        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);
        }
Пример #4
0
        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));
        }
Пример #8
0
        /// <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));
        }
Пример #12
0
        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);
        }
Пример #13
0
        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();
                }
            }
        }
Пример #15
0
        /// <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}.");
            }
        }
Пример #16
0
        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);
        }
Пример #18
0
        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);
        }