Make a httpclient static downloader

Viewed 83

I really need help. could someone help me make this codes as static? I got this somewhere here and I've added a little codes. But I've noticed the download process is slow because of not using httpclient as a single instance.

I wanna make this as static so that I can make the httpclient and handler as single instance for all the request.

I have tried to make it static but I failed because I can't trigger the progress percentage and the totalbytes from my main gui form.

Here's the full source code for the downloader.

class Downloader : IDisposable
    {
        private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36";
        private string DownloadUrl { get; set; }
        private string OutputFilePath { get; set; }

        private HttpClientHandler handler;
        private HttpClient client;

        public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
        public delegate void ProgressChangedHandler2(double? progressPercentage);
        public event ProgressChangedHandler ProgressChanged; //for label progress
        public Downloader(string downloadUrl, string outputFilePath)
        {
            DownloadUrl = downloadUrl;
            OutputFilePath = outputFilePath;
        }

        public async Task StartDownload(CancellationToken token)
        {
            handler = new HttpClientHandler()
            {
                Proxy = null,
                UseProxy = false,
                UseCookies = false,
                //SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
                //ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }
            };
            client = new HttpClient(handler);

            Uri uri = new Uri(DownloadUrl, UriKind.Absolute);

            SetConnection(uri);
            using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
            {
                request.RequestUri = uri;
                request.Headers.UserAgent.ParseAdd(UserAgent);

                using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token))
                {
                    await DownloadFileFromHttpResponseMessage(response, token);
                }
            }
        }
        private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response, CancellationToken token)
        {
            response.EnsureSuccessStatusCode();

            using (var contentStream = await response.Content.ReadAsStreamAsync())
            {
                long? totalBytes = response.Content.Headers.ContentLength;
                long totalBytesRead = 0L;
                long readCount = 0L;
                byte[] buffer = new byte[4096];
                bool isMoreToRead = true;

                using (var fileStream = new FileStream(OutputFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    do
                    {
                        int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, token);
                        if (bytesRead == 0)
                        {
                            isMoreToRead = false;
                            TriggerProgressChanged(totalBytes, totalBytesRead);
                            continue;
                        }

                        await fileStream.WriteAsync(buffer, 0, bytesRead);

                        totalBytesRead += bytesRead;
                        readCount += 1;

                        if (readCount % 10 == 0)
                        {
                            TriggerProgressChanged(totalBytes, totalBytesRead);
                        }
                    }
                    while (isMoreToRead);
                }
                TriggerProgressChanged(totalBytes, totalBytesRead);
            }
        }
        private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
        {
            if (ProgressChanged == null)
                return;

            double? progressPercentage = null;
            if (totalDownloadSize.HasValue)
                progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);

            ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
        }
        private void SetConnection(Uri uri)
        {
            var sp = ServicePointManager.FindServicePoint(uri);
            sp.ConnectionLimit = 20; //default 2 //The number of connections per end point.
            sp.UseNagleAlgorithm = false; //Nagle’s algorithm is a means of improving the efficiency of TCP/IP networks by reducing the number of packets that need to be sent over the network
            sp.Expect100Continue = false; //save bandwidth before sending huge object for post and put request to ensure remote end point is up and running.
            sp.ConnectionLeaseTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;//60 * 1000; // 1 minute
        }
        public void Dispose()
        {
            client?.Dispose();
        }
    }

And here's the code from my gui

using (var client = new Downloader(url, outputFilePath))
            {

                client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) =>
                {
                    sw.Start();

                    if (tryAbort) { PauseOrResumeProcess(); }

                    PB.Value = Convert.ToInt32(progressPercentage);
                    lvi.SubItems[2].Text = ($"{progressPercentage}%");
                    lvi.SubItems[3].Text = ($"{ Utils.FormatFileSize(totalBytesDownloaded)} of { Utils.FormatFileSize(totalFileSize.Value)}");
                };

                await client.StartDownload(ct);
            }
2 Answers

It would be better to add your HttpClient as scoped for your app, it means that there would be only one instance of HTTP client for each tread.

Just add services.AddScoped<HttpClient>(); to your ConfigureServices(IServiceCollection services) startup class and configure it there. And then:

public Downloader(string downloadUrl, string outputFilePath, HttpClient client)
{
    DownloadUrl = downloadUrl;
    OutputFilePath = outputFilePath;
    this.client = client;
}

Update: Alright here's what I've tried and it worked. I just removed also the IDisposable and only changed client and handler to static and I got the maximum download speed.

 private static readonly HttpClientHandler handler = new HttpClientHandler()
    {
        Proxy = null,
        UseProxy = false
    };
    private static readonly HttpClient client = new HttpClient(handler);
Related