HisDivineShadow
HisDivineShadow

Reputation: 1148

How to configure HttpClient in Azure functions

Like many I'm using the HttpClient in some of my Azure functions (v3.x). The issue is that Azure function host uses the HttpClient as a singleton. This means that if I set the headers in one function they're set for all functions. I want to have different headers (e.g. Authorization headers) in different functions. Is the only way to accomplish this is to create different function apps for each different set of headers that I need?

Upvotes: 8

Views: 8676

Answers (1)

Andy
Andy

Reputation: 13527

You typically don't use a "global" HttpClient's DefaultRequestHeaders. You would want to create an HttpRequestMessage and use SendAsync. This gives that session a unique header dictionary that is segregated from all other requests.

HttpClient's DefaultRequestHeaders are not thread-safe. So if you modify them in the middle of another request, you will crash.

What you should do is inject HttpClientFactory and then create clients from that. Continue to use the HttpRequestMessage/SendAsync pattern (or use straight-up GetAsync, PostAsync, etc.) all while avoiding DefaultRequestHeaders (if you use the factory's CreateClient() method).

If you use a factory, you can use DefaultRequestHeaders if you create a named or typed HttpClient object from the factory. You will probably want to do this for your specific use-case you mentioned in your question.

Example

  1. Start a new Azure Function project.
  2. Add Microsoft.Azure.Functions.Extensions and Microsoft.Extensions.Http packages via NuGet.
  3. Add a new class, name it Startup.cs. Make it look like this:
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http.Headers;

[assembly: FunctionsStartup(typeof(MyAzureFunction.Startup))]

namespace MyAzureFunction
{
    public sealed class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient("MyAuthorizedClient", client =>
            {
                client.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", "<token>");
            });
        }
    }
}
  1. By default, the function's class and the function itself is static. Make sure to remove the static keyword. Then, add a constructor. We will inject the factory via constructor:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace MyAzureFunction
{
    public sealed class Function1
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public Function1(IHttpClientFactory httpContextFactory)
        {
            _httpClientFactory = httpContextFactory;
        }

        [FunctionName("Function1")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
        {
            //
            // make a request on the authorized client
            var authorizedClient = _httpClientFactory.CreateClient("MyAuthorizedClient");
            
            using (var response = await authorizedClient
                .GetAsync(new Uri("https://www.example.com")))
            {
                var objRecved = await response.Content.ReadAsAsync<object>();

                return new OkObjectResult(new { data = objRecved });
            }

            //
            // make a request on the non-authorized client using it's own header collection
            var nonAuthorizedClient = _httpClientFactory.CreateClient();
            
            using (var request = new HttpRequestMessage(
                HttpMethod.Get, new Uri("https://www.example.com")))
            {
                request.Headers.Authorization =
                    new AuthenticationHeaderValue("Bearer", "<token>");
                using(var response = await nonAuthorizedClient.SendAsync(request))
                {
                    response.EnsureSuccessStatusCode();

                    var objRecved = await response.Content.ReadAsAsync<object>();

                    return new OkObjectResult(new { data = objRecved });
                }
            }
        }
    }
}

Upvotes: 8

Related Questions