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