Archive for 2012/02

ماژول دانلودر برای ASP.NET

۱۳۹۰/۱۲/۰۶ ۱۳:۰۴ Salar Khalilzadeh https://plus.google.com/105397214522932500988 منتشر شده در تاریخ : ۱۳۹۰/۱۲/۰۶ دسته بندی : ، ، ، 3

  • مقدمه
بیشتر مواقع لازم است تا بر روی دانلود های وبسایت نظارت انجام شود, برای مثال فقط اعضا اجازه دانلود فایلی را داشته و یا دانلود یک شمارنده داشته باشد و غیره. در حالت عادی برنامه نویسان فایل را مستقیم به خروجی می نویسند. این روش کار را انجام داده و به نتیجه لازم نیز می رسد اما یک مزیت بزرگ را از دست می رود. آن مزیت قابلیت Resumable Downloading است که به طور خلاصه به کاربر این اجازه را می دهد که در هر زمان دانلود را متوقف کرده و پس از شروع مجدد از همان محل قبلی ادامه دانلود را انجام دهد. این موارد بیشتر در فایلهای حجیم صادق است و مورد نیاز می شود.
همچنین یک نیاز دیگر هم پیش می آید و آن محدود کردن پهنای باند مصرفی برای دانلود است. که در حالت عادی و ارسال فایل از طریق دستورات asp.net این کار امکان پذیر نیست.

  • معرفی
برای رفع مشکلات و محدودیت های مطرح شده ماژولی را آماده کرده ام که با استفاده از آنها میتوانید کنترل نسبتا کاملی بر دانلود کاربر داشته باشید.
مجموعه کلاسهای ResumableDownload چندین کار مخلتف را انجام می دهد که با کنار هم قرار دادن آنها به مقصودمان می رسیم.

  • طریقه استفاده
قبل از هرکاری باید درخواست ورودی کاربر بررسی شده و دقیقا بدانیم که کدام فایل مد نظر است. سپس حجم آن را بدست بیاوریم.
اولین قدیم شناسایی درخواست کاربر است. این درخواست معمولا توسط یک نرم افزار دانلودر مانند IDM انجام شده است. هدف در اینجا شناسایی هدر های ارسالی است که توسط یکی از متدهای کلاس HeadersParser انجام می شود.
قدم دوم باید اطلاعات فایلی که قرار است دانلود بشود توسط کلاس DownloadDataInfo جمع آوری شود. یکی از سازنده های کلاس اطلاعات فایل را دریافت می کند باید مورد استفاده قرار گیرد. سپس خروجی متد مورد استفاده از HeadersParser باید به کلاس DownloadDataInfo اطلاع داده شود و این کار با استفاده از متد InitializeRanges آن انجام می شود.
قدم سوم بررسی صحت درخواست دانلود است. این کار توسط متد های Validate کلاس HeadersParser انجام می شود. این کار از این جهت مهم است که ممکن است فایل در سرور تغییر داده شده باشد ولی کاربر سعی دارد تا همچنان به دانلود نسخه قدیمی که ناقص انجام شده بود ادامه دهد.
قدم اختیاری در این بین اعمال محدودیت در پهنای باند دانلود فایل برای کاربر است. این محدودیت توسط کلاس UserSpeedLimitManager قابل اعمال است. در آزمایشاتی که من انجام دادم مشخص شد که در هر صورت استفاده از این کلاس بار زیادی را از دوش سرور بر می دارد. این بدان علت است که در حالت بدون محدودیت سرور تمام فایل را از دیسک خوانده و به یکباره به بافر انتظار می فرستد, این عملیات یکباره بار زیادی بر سرور وارد می کند. همچنین مشخص شد که گذاشتن محدودیت حتی در حد 2MB/s می تواند تاثیر زیادی در افزایش بازدهی سرور داشته باشد.
قدم نهایی ارسال فایل به کاربر طبق درخواست وی که از طرق متد ProcessDownload کلاس DownloadProcess انجام می شود.

در ادامه یک مثال کامل را مشاهده می کنید. در این مثال نام فایل به عنوان ورودی ارسال می شود, سیستم این فایل را از مسیر برنامه یافته و برای کاربر ارسال می کند.
برای مثال آدرس به این صورت خواهد بود: http://localhost:5200/ResumableDownload.ashx?file=sample.zip

// 50 KB limit
const int DownloadLimit = 50 * 1024;

public void ProcessRequest(HttpContext context)
{
 // Accepting user request

 // reading the query
 var fileNameQuery = context.Request.QueryString["file"];

 // validating the request
 if (string.IsNullOrEmpty(fileNameQuery))
 {
  InvalidRequest(context, "Invalid request! Specify file name in url e.g.: ResumableDownload.ashx?file=sample.zip");
  return;
 }

 // the physical file address path
 var fileName = context.Server.MapPath(fileNameQuery);
 if (!File.Exists(fileName))
 {
  InvalidRequest(context, "File does not exists!");
  return;
 }

 // reading file info
 var fileInfo = new FileInfo(fileName);
 var fileLength = fileInfo.Length;

 // Download information class
 var downloadInfo = new DownloadDataInfo(fileName);

 // Reading request download range
 var requestedRanges = HeadersParser.ParseHttpRequestHeaderMultipleRange(context.Request, fileLength);

 // apply the ranges to the download info
 downloadInfo.InitializeRanges(requestedRanges);

 string etagMatched;
 int outcomeStausCode = 200;

 // validating the ranges specified
 if (!HeadersParser.ValidatePartialRequest(context.Request, downloadInfo, out etagMatched, ref outcomeStausCode))
 {
  // the request is invalid, this is the invalid code
  context.Response.StatusCode = outcomeStausCode;

  // show to the client what is the real ETag
  if (!string.IsNullOrEmpty(etagMatched))
   context.Response.AppendHeader("ETag", etagMatched);

  // stop the preoccess
  // but don't hassle with error messages
  return;
 }

 // user ID, or IP or anything you use to identify the user
 var userIP = context.Request.UserHostAddress;

 // limiting the download speed manager and the speed limit
 UserSpeedLimitManager.StartNewDownload(downloadInfo, userIP, DownloadLimit);

 // It is very important to destory the DownloadProcess object
 // Here the using block does it for us.
 using (var process = new DownloadProcess(downloadInfo))
 {
  // start the download
  var state = process.ProcessDownload(context.Response);

  // checking the state of the download
  if (state == DownloadProcess.DownloadProcessState.PartFinished)
  {
   // all parts of download are finish, do something here!
  }
 }
}

 
  • دانلود
جهت دانلود از این آدرس و یا این آدرس اقدام کنید.
کدهای این پروژه به صورت آنلاین در این آدرس در دسترس هستند.

چندین کانکشن همزمان برای دانلود در حالی که کاربر به 50KB/s محدود شده است

  • مشکلات احتمالی
یک مشکل که فعلا حل نشده است و مربوط به محدودیت دانلود اعمال شده است که ممکن است دقیق اعمال نشود, برای مثال اگر محدودیت 50KB/s را اعمال کرده باشید سرعت دانلود کاربر بین 30 تا 60 در نوسان خواهد بود.

  • کدهای استفاده شده از سایر نویسندگان
کلاس ThrottledStream جهت اعمال محدودیت در پهنای باند مصرفی. اینجا.
نسخه ابتدایی و قدیمی ZipHandler در زبان vb.net که چند سال پیش این ماژول ها بر اساس آن تبدیل و تهیه شد. اینجا.