Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need Official Guidance On using HttpClient in Functions #1806

Closed
solvingj opened this issue Aug 18, 2017 · 5 comments
Closed

Need Official Guidance On using HttpClient in Functions #1806

solvingj opened this issue Aug 18, 2017 · 5 comments

Comments

@solvingj
Copy link

solvingj commented Aug 18, 2017

This issue seeks direct advice, potentially leading to a feature request for Azure Functions. It surrounds dependency injection for which there are numerous open discussions. It's possible this issue could be linked to the larger topic, but I think it's important to document a specific situation.

Main Problem
There is a well documented "design feature" of HttpClient, wherein Microsoft recommends to share a single instance across the life of an application. The reference is included below, along with two blog posts which demonstrate why this is important even at moderate scale using actual benchmarks.

While the issue is fairly well understood and documented now, all the articles and forum posts with recommendations on how to work around the limitations are rather primitive and only for demonstration. The only real implementation I've found was documented on Kloud.com.au, and it was specifically for Azure Functions.

The impact of the issue if ignored could be that certain Azure Functions which make high volumes of Http calls are regularly being subtly degraded, or the Functions could be scaling out unnecessarily. The nature of the issue makes it's impact very hard to quantify.

Additional Problem
The implementation on Kloud.com.au was a good solution for a time. However, it's based on a plugin/extra ("Service Locator") of a single DI framework (AutoFAC), with a dependency on a "legacy" microsoft package: "Common Service Locator". This dependency chain is precarious, and indeed now falling over. Common Service Locator is not being ported to Netstandard or Netcore, and so the Autofac extra can't be either. The original Kloud.com author forked, patched, and published a port to nuget which has everything working, but that requires Autofac version 4.3.0 or lower , while current Autofac version is 4.6.1. This is obviously undesirable.

Moving Forward
Again, this issue seeks direct advice, including suggestions for approaches. Here are two possibilities for your commentary, which are perhaps oversimplified but hopefully get the conversation started.

  1. Provide a robust, modern, and working example of a singleton pattern for HttpClient in Azure Functions which is ~supported, preferably with minimal additional dependencies that works under both 4.6.1 and Netstandard2. If it's not too bad to copy/paste, it would be fine.
  2. Provide some form of build-in DI for Azure Functions, which users can call upon to get handles to things like a singleton instance of HttpClient , or other things, such as a DocumentDB or AzureKeyVault client constructed from said singleton.

Recommendation
If the functions team was previously unaware of this issue, then I suggest starting with some research.
The references below provide easy techniques to force the issue, so running function-specific benchmarks seems trivial and worthwhile. Also, it would be interesting to see it's impact on functions scaling. If there is already some form of native DI in the works for azure functions, then please validate that it can service this situation and include it as an example in the reference documentation that comes with said DI.

References

https://blog.kloud.com.au/2016/11/21/managing-dependencies-in-azure-functions/

https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html

https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client

@solvingj
Copy link
Author

solvingj commented Aug 19, 2017

So today I found that there was a post about this regarding azure functions:
https://docs.microsoft.com/en-us/azure/azure-functions/functions-best-practices

Which linked to this instructive post:
https://docs.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/

Does that simple solution really solve the problem as safely and effectively for all use cases? I'm not sure, seems much simpler than using DI and a service locator pattern.

@christopheranderson
Copy link
Contributor

Yes. That's all that's needed. :)

@solvingj
Copy link
Author

I'll be giving it a try, but something tells me that static variables as the solution will not come without drawbacks.

@octocat-mona
Copy link

The links posted by @solvingj do not seem to explicitly mention HttpClient usage in Azure Functions?
I did find this wiki page explaining to just use a static HttpClient in a Function:

// Create a single, static HttpClient
private static HttpClient httpClient = new HttpClient();

public static async Task Run(string input)
{
    var response = await httpClient.GetAsync("http://example.com");
    // Rest of function
}

I wasn't sure that Function invocations would reuse instances, but apparently they do :)

@DonFrazier-zz
Copy link

Please see https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests Is the approach documented there a valid solution for HttpClient usage?

Similar to a asp.net core site I want to create an HttpClientSimilar to

    {
        private readonly HttpClient httpClient;
        public SlackClient(HttpClient httpClient, IOptions<SlackOptions> options)
        {
            this.httpClient = httpClient;
            this.httpClient.Authorization = new AuthHeaderValue("Bearer", options.Value.SlackToken);
        }

and in my startup class register it like

[assembly: FunctionsStartup(typeof(MyStartup))]
namespace Sample
{
    public class MyStartup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var config = new ConfigurationBuilder()
                .SetBasePath(Environment.CurrentDirectory)
                .AddJsonFile("local.settings.json", optional: true)
                .AddUserSecrets(Assembly.GetExecutingAssembly(), optional: true)
                .AddEnvironmentVariables()
                .Build();

            builder.Services.AddHttpClient<ISlackClient, SlackClient>();
...

@Azure Azure locked as resolved and limited conversation to collaborators Jan 2, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants