示例#1
0
        public async Task ResponseExtensions_SetBodyString_Success()
        {
            var r = new StreamingResponse();

            r.SetBody("123");

            Assert.NotNull(r.Streams);
            Assert.Single(r.Streams);
            Assert.Equal(typeof(StringContent), r.Streams[0].Content.GetType());

            var s = await r.Streams[0].Content.ReadAsStringAsync().ConfigureAwait(false);

            Assert.Equal("123", s);
        }
示例#2
0
        public void ResponseExtensions_SetBody_Null_Does_Not_Throw()
        {
            var       r  = new StreamingResponse();
            Exception ex = null;

            try
            {
                r.SetBody(null);
            }
            catch (Exception caughtEx)
            {
                ex = caughtEx;
            }
            finally
            {
                Assert.Null(ex);
            }
        }
示例#3
0
        public async Task ResponseExtensions_SetBody_Success()
        {
            var r = new StreamingResponse();
            var a = new Activity()
            {
                Text = "hi", Type = "message"
            };

            r.SetBody(a);

            Assert.NotNull(r.Streams);
            Assert.Single(r.Streams);
            Assert.Equal(typeof(StringContent), r.Streams[0].Content.GetType());

            var s = JsonConvert.DeserializeObject <Activity>(await r.Streams[0].Content.ReadAsStringAsync().ConfigureAwait(false));

            Assert.Equal(a.Text, s.Text);
            Assert.Equal(a.Type, s.Type);
        }
        public async Task ResponseExtensions_SetBody_Success()
        {
            var r = new StreamingResponse();
            var a = new Activity()
            {
                Text = "hi", Type = "message"
            };

            r.SetBody(a);

            Assert.IsNotNull(r.Streams);
            Assert.AreEqual(1, r.Streams.Count);
            Assert.AreEqual(typeof(StringContent), r.Streams[0].Content.GetType());

            var s = await r.Streams[0].Content.ReadAsAsync <Activity>().ConfigureAwait(false);

            Assert.AreEqual(a.Text, s.Text);
            Assert.AreEqual(a.Type, s.Type);
        }
示例#5
0
        /// <summary>
        /// Checks the validity of the request and attempts to map it the correct custom endpoint,
        /// then generates and returns a response if appropriate.
        /// </summary>
        /// <param name="request">A ReceiveRequest from the connected channel.</param>
        /// <param name="response">The <see cref="StreamingResponse"/> instance.</param>
        /// <returns>A response if the given request matches against a defined path.</returns>
        private StreamingResponse HandleCustomPaths(ReceiveRequest request, StreamingResponse response)
        {
            if (request == null || string.IsNullOrEmpty(request.Verb) || string.IsNullOrEmpty(request.Path))
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                _logger.LogError("Request missing verb and/or path.");

                return(response);
            }

            if (string.Equals(request.Verb, StreamingRequest.GET, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(request.Path, "/api/version", StringComparison.OrdinalIgnoreCase))
            {
                response.StatusCode = (int)HttpStatusCode.OK;
                response.SetBody(new VersionInfo()
                {
                    UserAgent = _userAgent
                });

                return(response);
            }

            return(null);
        }
示例#6
0
        /// <summary>
        /// Checks the validity of the request and attempts to map it the correct virtual endpoint,
        /// then generates and returns a response if appropriate.
        /// </summary>
        /// <param name="request">A ReceiveRequest from the connected channel.</param>
        /// <param name="logger">Optional logger used to log request information and error details.</param>
        /// <param name="context">Optional context to operate within. Unused in bot implementation.</param>
        /// /// <param name="cancellationToken">Optional cancellation token.</param>
        /// <returns>A response created by the BotAdapter to be sent to the client that originated the request.</returns>
        public override async Task <StreamingResponse> ProcessRequestAsync(ReceiveRequest request, ILogger <RequestHandler> logger, object context = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            logger = logger ?? NullLogger <RequestHandler> .Instance;
            var response = new StreamingResponse();

            if (request == null || string.IsNullOrEmpty(request.Verb) || string.IsNullOrEmpty(request.Path))
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                logger.LogError("Request missing verb and/or path.");

                return(response);
            }

            if (string.Equals(request.Verb, StreamingRequest.GET, StringComparison.InvariantCultureIgnoreCase) &&
                string.Equals(request.Path, "/api/version", StringComparison.InvariantCultureIgnoreCase))
            {
                response.StatusCode = (int)HttpStatusCode.OK;
                response.SetBody(new VersionInfo()
                {
                    UserAgent = _userAgent
                });

                return(response);
            }

            if (string.Equals(request.Verb, StreamingRequest.POST, StringComparison.InvariantCultureIgnoreCase) &&
                string.Equals(request.Path, "/api/messages", StringComparison.InvariantCultureIgnoreCase))
            {
                return(await ProcessStreamingRequestAsync(request, response, logger, cancellationToken).ConfigureAwait(false));
            }

            response.StatusCode = (int)HttpStatusCode.NotFound;
            logger.LogError($"Unknown verb and path: {request.Verb} {request.Path}");

            return(response);
        }
示例#7
0
        /// <summary>
        /// Handles incoming requests.
        /// </summary>
        /// <param name="request">A <see cref="ReceiveRequest"/> for this handler to process.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="context">Optional context to process the request within.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>A <see cref="Task"/> that will produce a <see cref="StreamingResponse"/> on successful completion.</returns>
        public override async Task <StreamingResponse> ProcessRequestAsync(ReceiveRequest request, ILogger <RequestHandler> logger = null, object context = null, CancellationToken cancellationToken = default)
        {
            var response = new StreamingResponse();

            // We accept all POSTs regardless of path, but anything else requires special treatment.
            if (!string.Equals(request?.Verb, StreamingRequest.POST, StringComparison.OrdinalIgnoreCase))
            {
                return(HandleCustomPaths(request, response));
            }

            // Convert the StreamingRequest into an activity the adapter can understand.
            string body;

            try
            {
                body = request.ReadBodyAsString();
            }
#pragma warning disable CA1031 // Do not catch general exception types (we log the exception and continue execution)
            catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                _logger.LogError("Request body missing or malformed: " + ex.Message);

                return(response);
            }

            try
            {
                var activity = JsonConvert.DeserializeObject <Activity>(body, SerializationSettings.DefaultDeserializationSettings);

                // All activities received by this StreamingRequestHandler will originate from the same channel, but we won't
                // know what that channel is until we've received the first request.
                if (string.IsNullOrWhiteSpace(ServiceUrl))
                {
                    ServiceUrl = activity.ServiceUrl;
                }

                // If this is the first time the handler has seen this conversation it needs to be added to the dictionary so the
                // adapter is able to route requests to the correct handler.
                if (!HasConversation(activity.Conversation.Id))
                {
                    _conversations.Add(activity.Conversation.Id, DateTime.Now);
                }

                /*
                 * Any content sent as part of a StreamingRequest, including the request body
                 * and inline attachments, appear as streams added to the same collection. The first
                 * stream of any request will be the body, which is parsed and passed into this method
                 * as the first argument, 'body'. Any additional streams are inline attachments that need
                 * to be iterated over and added to the Activity as attachments to be sent to the Bot.
                 */
                if (request.Streams.Count > 1)
                {
                    var streamAttachments = new List <Attachment>();
                    for (var i = 1; i < request.Streams.Count; i++)
                    {
                        streamAttachments.Add(new Attachment()
                        {
                            ContentType = request.Streams[i].ContentType, Content = request.Streams[i].Stream
                        });
                    }

                    if (activity.Attachments != null)
                    {
                        activity.Attachments = activity.Attachments.Concat(streamAttachments).ToArray();
                    }
                    else
                    {
                        activity.Attachments = streamAttachments.ToArray();
                    }
                }

                // Now that the request has been converted into an activity we can send it to the adapter.
                var adapterResponse = await _activityProcessor.ProcessStreamingActivityAsync(activity, _bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);

                // Now we convert the invokeResponse returned by the adapter into a StreamingResponse we can send back to the channel.
                if (adapterResponse == null)
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                }
                else
                {
                    response.StatusCode = adapterResponse.Status;
                    if (adapterResponse.Body != null)
                    {
                        response.SetBody(adapterResponse.Body);
                    }
                }
            }
#pragma warning disable CA1031 // Do not catch general exception types (we logging the error and we send it back in the body of the response)
            catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
            {
                response.StatusCode = (int)HttpStatusCode.InternalServerError;
                response.SetBody(ex.ToString());
                _logger.LogError(ex.ToString());
            }

            return(response);
        }
示例#8
0
        /// <summary>
        /// Handles incoming requests.
        /// </summary>
        /// <param name="request">A <see cref="ReceiveRequest"/> for this handler to process.</param>
        /// <param name="logger">Logger.</param>
        /// <param name="context">Optional context to process the request within.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>A <see cref="Task"/> that will produce a <see cref="StreamingResponse"/> on successful completion.</returns>
        public override async Task <StreamingResponse> ProcessRequestAsync(ReceiveRequest request, ILogger <RequestHandler> logger = null, object context = null, CancellationToken cancellationToken = default)
        {
            var response = new StreamingResponse();

            // We accept all POSTs regardless of path, but anything else requires special treatment.
            if (!string.Equals(request?.Verb, StreamingRequest.POST, StringComparison.OrdinalIgnoreCase))
            {
                return(HandleCustomPaths(request, response));
            }

            // Convert the StreamingRequest into an activity the adapter can understand.
            string body;

            try
            {
                body = await request.ReadBodyAsStringAsync().ConfigureAwait(false);
            }
#pragma warning disable CA1031 // Do not catch general exception types (we log the exception and continue execution)
            catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                _logger.LogError("Request body missing or malformed: " + ex.Message);

                return(response);
            }

            try
            {
                var activity = JsonConvert.DeserializeObject <Activity>(body, SerializationSettings.DefaultDeserializationSettings);

                // All activities received by this StreamingRequestHandler will originate from the same channel, but we won't
                // know what that channel is until we've received the first request.
                if (string.IsNullOrWhiteSpace(ServiceUrl))
                {
                    ServiceUrl = activity.ServiceUrl;
                }

                // If this is the first time the handler has seen this conversation it needs to be added to the dictionary so the
                // adapter is able to route requests to the correct handler.
                if (!HasConversation(activity.Conversation.Id))
                {
                    _conversations.Add(activity.Conversation.Id, DateTime.Now);
                }

                /*
                 * Any content sent as part of a StreamingRequest, including the request body
                 * and inline attachments, appear as streams added to the same collection. The first
                 * stream of any request will be the body, which is parsed and passed into this method
                 * as the first argument, 'body'. Any additional streams are inline attachments that need
                 * to be iterated over and added to the Activity as attachments to be sent to the Bot.
                 */
                if (request.Streams.Count > 1)
                {
                    var streamAttachments = new List <Attachment>();
                    for (var i = 1; i < request.Streams.Count; i++)
                    {
                        streamAttachments.Add(new Attachment()
                        {
                            ContentType = request.Streams[i].ContentType, Content = request.Streams[i].Stream
                        });
                    }

                    if (activity.Attachments != null)
                    {
                        activity.Attachments = activity.Attachments.Concat(streamAttachments).ToArray();
                    }
                    else
                    {
                        activity.Attachments = streamAttachments.ToArray();
                    }
                }

                // Populate Activity.CallerId given the Audience value.
                string callerId = null;
                switch (Audience)
                {
                case AuthenticationConstants.ToChannelFromBotOAuthScope:
                    callerId = CallerIdConstants.PublicAzureChannel;
                    break;

                case GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope:
                    callerId = CallerIdConstants.USGovChannel;
                    break;

                default:
                    if (!string.IsNullOrEmpty(Audience))
                    {
                        if (Guid.TryParse(Audience, out var result))
                        {
                            // Large assumption drawn here; any GUID is an AAD AppId. This is prohibitive towards bots not using the Bot Framework auth model
                            // but still using GUIDs/UUIDs as identifiers.
                            // It's also indicative of the tight coupling between the Bot Framework protocol, authentication and transport mechanism in the SDK.
                            // In R12, this work will be re-implemented to better utilize the CallerId and Audience set on BotFrameworkAuthentication instances
                            // and decouple the three concepts mentioned above.
                            callerId = $"{CallerIdConstants.BotToBotPrefix}{Audience}";
                        }
                        else
                        {
                            // Fallback to using the raw Audience as the CallerId. The auth model being used by the Adapter using this StreamingRequestHandler
                            // is not known to the SDK, therefore it is assumed the developer knows what they're doing. The SDK should not prevent
                            // the developer from extending it to use custom auth models in Streaming contexts.
                            callerId = Audience;
                        }
                    }

                    // A null Audience is an implicit statement indicating the bot does not support skills.
                    break;
                }

                activity.CallerId = callerId;

                // Now that the request has been converted into an activity we can send it to the adapter.
                var adapterResponse = await _activityProcessor.ProcessStreamingActivityAsync(activity, _bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);

                // Now we convert the invokeResponse returned by the adapter into a StreamingResponse we can send back to the channel.
                if (adapterResponse == null)
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                }
                else
                {
                    response.StatusCode = adapterResponse.Status;
                    if (adapterResponse.Body != null)
                    {
                        response.SetBody(adapterResponse.Body);
                    }
                }
            }
#pragma warning disable CA1031 // Do not catch general exception types (we logging the error and we send it back in the body of the response)
            catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
            {
                response.StatusCode = (int)HttpStatusCode.InternalServerError;
                response.SetBody(ex.ToString());
                _logger.LogError(ex.ToString());
            }

            return(response);
        }
示例#9
0
        /// <summary>
        /// Performs the actual processing of a request, handing it off to the adapter and returning the response.
        /// </summary>
        /// <param name="request">A ReceiveRequest from the connected channel.</param>
        /// <param name="response">The response to update and return, ultimately sent to client.</param>
        /// <param name="logger">Optional logger.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>The response ready to send to the client.</returns>
        private async Task <StreamingResponse> ProcessStreamingRequestAsync(ReceiveRequest request, StreamingResponse response, ILogger <RequestHandler> logger, CancellationToken cancellationToken)
        {
            var body = string.Empty;

            try
            {
                body = request.ReadBodyAsString();
            }
            catch (Exception ex)
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                logger.LogError("Request body missing or malformed: " + ex.Message);

                return(response);
            }

            try
            {
                var  adapter = new BotFrameworkStreamingExtensionsAdapter(_transportServer, _middlewareSet, logger);
                IBot bot     = null;

                // First check if an IBot type definition is available from the service provider.
                if (_services != null)
                {
                    /* Creating a new scope for each request allows us to support
                     * bots that inject scoped services as dependencies.
                     */
                    bot = _services.CreateScope().ServiceProvider.GetService <IBot>();
                }

                // If no bot has been set, check if a singleton bot is associated with this request handler.
                if (bot == null)
                {
                    bot = _bot;
                }

                // If a bot still hasn't been set, the request will not be handled correctly, so throw and terminate.
                if (bot == null)
                {
                    throw new Exception("Unable to find bot when processing request.");
                }

                adapter.OnTurnError = _onTurnError;
                var invokeResponse = await adapter.ProcessActivityAsync(body, request.Streams, new BotCallbackHandler(bot.OnTurnAsync), cancellationToken).ConfigureAwait(false);

                if (invokeResponse == null)
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                }
                else
                {
                    response.StatusCode = invokeResponse.Status;
                    if (invokeResponse.Body != null)
                    {
                        response.SetBody(invokeResponse.Body);
                    }
                }

                invokeResponse = null;
            }
            catch (Exception ex)
            {
                response.StatusCode = (int)HttpStatusCode.InternalServerError;
                logger.LogError(ex.Message);
            }

            return(response);
        }
示例#10
0
        public async override Task <StreamingResponse> ProcessRequestAsync(ReceiveRequest request, ILogger <RequestHandler> logger = null, object context = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (Bot == null)
            {
                throw new ArgumentNullException(nameof(Bot));
            }

            if (SkillWebSocketBotAdapter == null)
            {
                throw new ArgumentNullException(nameof(SkillWebSocketBotAdapter));
            }

            var response = new StreamingResponse();

            var body = request.ReadBodyAsString();

            if (string.IsNullOrEmpty(body) || request.Streams?.Count == 0)
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                response.SetBody("Empty request body.");
                return(response);
            }

            if (request.Streams.Where(x => x.ContentType != "application/json; charset=utf-8").Any())
            {
                response.StatusCode = (int)HttpStatusCode.NotAcceptable;
                return(response);
            }

            Activity activity = null;

            try
            {
                activity = JsonConvert.DeserializeObject <Activity>(body, Serialization.Settings);
            }
            catch (Exception ex)
            {
                _botTelemetryClient.TrackException(ex);

                response.StatusCode = (int)HttpStatusCode.BadRequest;
                response.SetBody("Request body is not an Activity instance.");
                return(response);
            }

            if (_claimsIdentity.AuthenticationType != "anonymous")
            {
                var appIdClaimName = AuthHelpers.GetAppIdClaimName(_claimsIdentity);

                // retrieve the appid and use it to populate callerId on the activity
                activity.CallerId = _claimsIdentity.Claims.FirstOrDefault(c => c.Type == appIdClaimName)?.Value;
            }

            try
            {
                var cancellationTokenSource = new CancellationTokenSource();
                _stopWatch.Start();
                var invokeResponse = await this.SkillWebSocketBotAdapter.ProcessActivityAsync(activity, new BotCallbackHandler(this.Bot.OnTurnAsync), cancellationTokenSource.Token).ConfigureAwait(false);

                _stopWatch.Stop();

                _botTelemetryClient.TrackEvent("SkillWebSocketProcessRequestLatency", null, new Dictionary <string, double>
                {
                    { "Latency", _stopWatch.ElapsedMilliseconds },
                });

                // trigger cancel token after activity is handled. this will stop the typing indicator
                cancellationTokenSource.Cancel();

                if (invokeResponse == null)
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                }
                else
                {
                    response.StatusCode = invokeResponse.Status;
                    if (invokeResponse.Body != null)
                    {
                        response.SetBody(invokeResponse.Body);
                    }
                }
            }
            catch (SkillWebSocketCallbackException ex)
            {
                _botTelemetryClient.TrackException(ex);

                response.StatusCode = (int)HttpStatusCode.InternalServerError;
                response.SetBody(ex.Message);

                return(response);
            }
            catch (Exception ex)
            {
                _botTelemetryClient.TrackException(ex);

                response.StatusCode = (int)HttpStatusCode.InternalServerError;
            }

            return(response);
        }