// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Teams.Core.Hosting;
namespace Microsoft.Teams.Core.Http;
///
/// Provides shared HTTP request functionality for bot clients.
///
/// The HTTP client instance used to send requests.
/// The logger instance used for logging. Optional.
public class BotHttpClient(HttpClient httpClient, ILogger? logger = null)
{
private const string UserAgent = "teams.net/" + ThisAssembly.NuGetPackageVersion;
private static readonly JsonSerializerOptions DefaultJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
///
/// Sends an HTTP request and deserializes the response.
///
/// The type to deserialize the response to.
/// The HTTP method to use.
/// The full URL for the request.
/// The request body content. Optional.
/// The request options. Optional.
/// A cancellation token that can be used to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains the deserialized response, or null if the response is empty or 404 (when ReturnNullOnNotFound is true).
/// Thrown if the request fails and the failure is not handled by options.
public async Task SendAsync(
HttpMethod method,
string url,
string? body = null,
BotRequestOptions? options = null,
CancellationToken cancellationToken = default)
{
options ??= new BotRequestOptions();
using HttpRequestMessage request = CreateRequest(method, url, body, options);
logger?.HttpRequestSending(method, url, body);
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
logger?.HttpResponseReceived(method, url, (int)response.StatusCode);
return await HandleResponseAsync(response, method, url, options, cancellationToken).ConfigureAwait(false);
}
///
/// Sends an HTTP request with query parameters and deserializes the response.
///
/// The type to deserialize the response to.
/// The HTTP method to use.
/// The base URL for the request.
/// The endpoint path to append to the base URL.
/// The query parameters to include in the request. Optional.
/// The request body content. Optional.
/// The request options. Optional.
/// A cancellation token that can be used to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains the deserialized response, or null if the response is empty or 404 (when ReturnNullOnNotFound is true).
/// Thrown if the request fails and the failure is not handled by options.
public async Task SendAsync(
HttpMethod method,
string baseUrl,
string endpoint,
Dictionary? queryParams = null,
string? body = null,
BotRequestOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(baseUrl);
ArgumentNullException.ThrowIfNull(endpoint);
string fullPath = $"{baseUrl.TrimEnd('/')}/{endpoint.TrimStart('/')}";
string url = queryParams?.Count > 0
? QueryHelpers.AddQueryString(fullPath, queryParams)
: fullPath;
return await SendAsync(method, url, body, options, cancellationToken).ConfigureAwait(false);
}
///
/// Sends an HTTP request without expecting a response body.
///
/// The HTTP method to use.
/// The full URL for the request.
/// The request body content. Optional.
/// The request options. Optional.
/// A cancellation token that can be used to cancel the operation.
/// A task that represents the asynchronous operation.
/// Thrown if the request fails.
public async Task SendAsync(
HttpMethod method,
string url,
string? body = null,
BotRequestOptions? options = null,
CancellationToken cancellationToken = default)
{
await SendAsync