اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
پنج دقیقه
پس از آشنایی با «نکات دریافت فایلهای حجیم توسط HttpClient»، در ادامه میتوان سه قابلیت مهم از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایلهای حجیم را با HttpClient، همانند برنامههای download manager نیز پیاده سازی کرد.
از سرگیری مجدد درخواست ارسالی توسط HttpClient
یک نمونه از سرگیری مجدد درخواست را در مطلب «اضافه کردن قابلیت از سرگیری مجدد (resume) به HttpWebRequest» پیشتر در این سایت مطالعه کردهاید. اصول کلی آن نیز در اینجا صادق است. HTTP 1.1 از مفهوم range headers، برای دریافت پاسخهای جزئی پشتیبانی میکند. به این ترتیب در صورت پیاده سازی چنین قابلیتی در برنامهی سمت سرور، میتوان دریافت بازهای از بایتها را بجای دریافت فایل از ابتدا، از سرور درخواست کرد. به یک چنین قابلیتی Resume و یا از سرگیری مجدد گرفته میشود و درحین دریافت فایلهای حجیم بسیار حائز اهمیت است.
در اینجا نحوهی تنظیم یک RangeHeader را مشاهده میکنید. ابتدا نیاز است بررسی کنیم آیا فایل دریافتی از پیش موجود است؟ آیا قسمتی از این درخواست پیشتر دریافت شده و محتوای آن هم اکنون به صورت ذخیره شده وجود دارد؟ اگر بله، درخواست دریافت این فایل را بر اساس اندازهی دریافتی فعلی آن، به سرور ارائه میکنیم.
یک نکته: تمام وب سرورها و یا برنامههای وب از یک چنین قابلیتی پشتیبانی نمیکنند.
روش تشخیص آن نیز به صورت زیر است:
پس از خواندن هدر درخواست، اگر خاصیت AcceptRanges آن نال بود، یعنی قابلیت از سرگیری مجدد را ندارد. در این حالت باید فایل موجود فعلی را حذف و یا از نو (FileMode.CreateNew) بازنویسی کرد (بجای حالت FileMode.Append).
لغو درخواست ارسالی توسط HttpClient
پس از شروع غیرهمزمان client.GetAsync میتوان متد CancelPendingRequests آنرا فراخوانی کرد تا کلیه درخواستهای مرتبط با این client لغو شوند. اما این متد صرفا برای حالت پیشفرض client.GetAsync که دریافت هدر + محتوا است کار میکند (یعنی حالت HttpCompletionOption.ResponseContentRead). اگر همانند نکات بررسی شدهی در مطلب «دریافت فایلهای حجیم توسط HttpClient» صرفا درخواست خواندن هدر را بدهیم (HttpCompletionOption.ResponseHeadersRead)، چون کنترل ادامهی بحث را خودمان بر عهده گرفتهایم، لغو آن نیز به عهدهی خودمان است و متد CancelPendingRequests بر روی آن تاثیر نخواهد داشت.
این نکته در مورد تنظیم خاصیت TimeOut نیز صادق است. این خاصیت فقط زمانیکه دریافت کل هدر + محتوا توسط متد GetAsync مدیریت شوند، تاثیر گذار است.
بنابراین درحالتیکه نیاز به کنترل بیشتر است، هرچند فراخوانی متد CancelPendingRequests ضرری ندارد، اما الزاما سبب قطع کل درخواست نمیشود و باید این لغو را به صورت ذیل پیاده سازی کرد:
ابتدا یک منبع توکن لغو عملیات را به صورت ذیل ایجاد میکنیم:
سپس، متد لغو برنامه، تنها کافی است متد Cancel این cts را فراخوانی کند؛ تا عملیات دریافت فایل خاتمه یابد.
پس از این فراخوانی (()cts.Cancel)، نحوهی واکنش به آن به صورت ذیل خواهد بود:
در اینجا از cts.Token به عنوان پارامتر سوم متد GetAsync استفاده شدهاست. همچنین قسمت ثبت اطلاعات دریافتی، در استریم خروجی نیز به صورت یک حلقه درآمدهاست تا بتوان خاصیت IsCancellationRequested این توکن لغو را بررسی کرد و نسبت به آن واکنش نشان داد.
سعی مجدد درخواست ارسالی توسط HttpClient
یک روش پیاده سازی سعی مجدد درخواست شکست خورده، توسط کتابخانهی Polly است. روش دیگر آن نیز به صورت ذیل است:
در اینجا متد doDownloadFileAsync، پیاده سازی همان متدی است که در قسمت «لغو درخواست ارسالی توسط HttpClient» در مورد آن بحث شد. این قسمت دریافت فایل را در یک حلقه که حداقل یکبار اجرا میشود، قرار میدهیم. متد GetAsync استثناءهایی مانند TaskCanceledException (در حین TimeOut و یا فراخوانی متد CancelPendingRequests که البته همانطور که توضیح داده شد، بر روی روش کنترل Response تاثیری ندارند)، HttpRequestException پس از فراخوانی متد response.EnsureSuccessStatusCode (جهت اطمینان حاصل کردن از دریافت پاسخی بدون مشکل از طرف سرور) و یا SocketException و WebException را درصورت بروز مشکلی در شبکه، صادر میکند. نیازی به بررسی سایر استثناءها در اینجا نیست.
اگر یکی از این استثناءهای یاد شده رخدادند، اندکی صبر کرده و مجددا درخواست را از ابتدا صادر میکنیم.
در پایان این سعیهای مجدد، اگر استثنایی ثبت شده بود و همچنین عملیات نیز با موفقیت به پایان نرسیده بود، آنرا به فراخوان صادر میکنیم.
از سرگیری مجدد درخواست ارسالی توسط HttpClient
یک نمونه از سرگیری مجدد درخواست را در مطلب «اضافه کردن قابلیت از سرگیری مجدد (resume) به HttpWebRequest» پیشتر در این سایت مطالعه کردهاید. اصول کلی آن نیز در اینجا صادق است. HTTP 1.1 از مفهوم range headers، برای دریافت پاسخهای جزئی پشتیبانی میکند. به این ترتیب در صورت پیاده سازی چنین قابلیتی در برنامهی سمت سرور، میتوان دریافت بازهای از بایتها را بجای دریافت فایل از ابتدا، از سرور درخواست کرد. به یک چنین قابلیتی Resume و یا از سرگیری مجدد گرفته میشود و درحین دریافت فایلهای حجیم بسیار حائز اهمیت است.
var fileInfo = new FileInfo(outputFilePath); long resumeOffset = 0; if (fileInfo.Exists) { resumeOffset = fileInfo.Length; } if (resumeOffset > 0) { _client.DefaultRequestHeaders.Range = new RangeHeaderValue(resumeOffset, null); }
یک نکته: تمام وب سرورها و یا برنامههای وب از یک چنین قابلیتی پشتیبانی نمیکنند.
روش تشخیص آن نیز به صورت زیر است:
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); if (response.Headers.AcceptRanges == null && resumeOffset > 0) { // resume not supported, starting over }
لغو درخواست ارسالی توسط HttpClient
پس از شروع غیرهمزمان client.GetAsync میتوان متد CancelPendingRequests آنرا فراخوانی کرد تا کلیه درخواستهای مرتبط با این client لغو شوند. اما این متد صرفا برای حالت پیشفرض client.GetAsync که دریافت هدر + محتوا است کار میکند (یعنی حالت HttpCompletionOption.ResponseContentRead). اگر همانند نکات بررسی شدهی در مطلب «دریافت فایلهای حجیم توسط HttpClient» صرفا درخواست خواندن هدر را بدهیم (HttpCompletionOption.ResponseHeadersRead)، چون کنترل ادامهی بحث را خودمان بر عهده گرفتهایم، لغو آن نیز به عهدهی خودمان است و متد CancelPendingRequests بر روی آن تاثیر نخواهد داشت.
این نکته در مورد تنظیم خاصیت TimeOut نیز صادق است. این خاصیت فقط زمانیکه دریافت کل هدر + محتوا توسط متد GetAsync مدیریت شوند، تاثیر گذار است.
بنابراین درحالتیکه نیاز به کنترل بیشتر است، هرچند فراخوانی متد CancelPendingRequests ضرری ندارد، اما الزاما سبب قطع کل درخواست نمیشود و باید این لغو را به صورت ذیل پیاده سازی کرد:
ابتدا یک منبع توکن لغو عملیات را به صورت ذیل ایجاد میکنیم:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
پس از این فراخوانی (()cts.Cancel)، نحوهی واکنش به آن به صورت ذیل خواهد بود:
var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, _cts.Token); using(var stream = await result.Content.ReadAsStreamAsync()) { byte[] buffer = new byte[80000]; int bytesRead; while((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0 && !_cts.IsCancellationRequested) { outputStream.Write(buffer, 0, bytesRead); } }
سعی مجدد درخواست ارسالی توسط HttpClient
یک روش پیاده سازی سعی مجدد درخواست شکست خورده، توسط کتابخانهی Polly است. روش دیگر آن نیز به صورت ذیل است:
public async Task DownloadFileAsync(string url, string outputFilePath, int maxRequestAutoRetries) { var exceptions = new List<Exception>(); do { --maxRequestAutoRetries; try { await doDownloadFileAsync(url, outputFilePath); } catch (TaskCanceledException ex) { exceptions.Add(ex); } catch (HttpRequestException ex) { exceptions.Add(ex); } catch (Exception ex) when (isNetworkError(ex)) { exceptions.Add(ex); } // Wait a bit and try again later if (exceptions.Any()) await Task.Delay(2000, _cts.Token); } while (maxRequestAutoRetries > 0 && !_cts.IsCancellationRequested); var uniqueExceptions = exceptions.Distinct().ToList(); if (uniqueExceptions.Any()) { if (uniqueExceptions.Count() == 1) throw uniqueExceptions.First(); throw new AggregateException("Could not process the request.", uniqueExceptions); } } private static bool isNetworkError(Exception ex) { if (ex is SocketException || ex is WebException) return true; if (ex.InnerException != null) return isNetworkError(ex.InnerException); return false; }
اگر یکی از این استثناءهای یاد شده رخدادند، اندکی صبر کرده و مجددا درخواست را از ابتدا صادر میکنیم.
در پایان این سعیهای مجدد، اگر استثنایی ثبت شده بود و همچنین عملیات نیز با موفقیت به پایان نرسیده بود، آنرا به فراخوان صادر میکنیم.