ترفندهای جدید در XMLHttpRequest2

معرفی

یکی از قهرمانان گمنام در جهان HTML5 XMLHttpRequest است. به طور دقیق XHR2 HTML5 نیست. با این حال، این بخشی از پیشرفت‌های تدریجی است که فروشندگان مرورگر در پلتفرم اصلی ایجاد می‌کنند. من XHR2 را در کیف جدیدمان گنجانده ام زیرا نقش مهمی در برنامه های پیچیده وب امروزی ایفا می کند.

به نظر می رسد دوست قدیمی ما تغییرات زیادی داشته است اما بسیاری از مردم از ویژگی های جدید آن بی اطلاع هستند. XMLHttpRequest Level 2 مجموعه ای از قابلیت های جدید را معرفی می کند که به هک های پیچیده در برنامه های وب ما پایان می دهد. مواردی مانند درخواست های متقاطع، آپلود رویدادهای پیشرفت، و پشتیبانی از آپلود/دانلود داده های باینری. اینها به AJAX اجازه می‌دهند تا با بسیاری از APIهای HTML5 لبه‌ای مانند File System API ، Web Audio API و WebGL هماهنگ کار کند.

این آموزش برخی از ویژگی های جدید XMLHttpRequest را برجسته می کند، به ویژه آنهایی که می توانند برای کار با فایل ها استفاده شوند.

در حال واکشی داده ها

واکشی یک فایل به عنوان یک حباب باینری با XHR دردناک بوده است. از نظر فنی، حتی ممکن نبود. یکی از ترفندهایی که به خوبی مستند شده است شامل نادیده گرفتن نوع mime با مجموعه نویسه های تعریف شده توسط کاربر است که در زیر مشاهده می شود.

روش قدیمی برای واکشی تصویر:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);

// Hack to pass bytes through unprocessed.
xhr.overrideMimeType('text/plain; charset=x-user-defined');

xhr.onreadystatechange = function(e) {
  if (this.readyState == 4 && this.status == 200) {
    var binStr = this.responseText;
    for (var i = 0, len = binStr.length; i < len; ++i) {
      var c = binStr.charCodeAt(i);
      //String.fromCharCode(c & 0xff);
      var byte = c & 0xff;  // byte at offset i
    }
  }
};

xhr.send();

در حالی که این کار می کند، چیزی که شما واقعاً در responseText دریافت می کنید یک حباب باینری نیست. این یک رشته باینری است که فایل تصویر را نشان می دهد. ما سرور را فریب می‌دهیم تا داده‌ها را پردازش نشده پس بدهد. با وجود اینکه این جواهر کوچک کار می کند، من آن را جادوی سیاه می نامم و به آن توصیه نمی کنم. هر زمان که به هک کد کاراکتر و دستکاری رشته ها برای وادار کردن داده ها به فرمت مطلوب متوسل می شوید، این یک مشکل است.

تعیین فرمت پاسخ

در مثال قبلی، تصویر را به عنوان یک "فایل" باینری با نادیده گرفتن نوع mime سرور و پردازش متن پاسخ به عنوان یک رشته باینری دانلود کردیم. در عوض، بیایید از ویژگی‌های responseType و response جدید XMLHttpRequest استفاده کنیم تا به مرورگر اطلاع دهیم که می‌خواهیم داده‌ها به چه فرمتی بازگردانده شوند.

xhr. نوع پاسخ
قبل از ارسال درخواست، بسته به نیازهای داده، xhr.responseType را روی «text»، «arraybuffer»، «blob» یا «document» تنظیم کنید. توجه داشته باشید، تنظیم xhr.responseType = '' (یا حذف) به طور پیش فرض پاسخ را به "متن" می کند.
xhr. واکنش
پس از یک درخواست موفقیت آمیز، ویژگی پاسخ xhr حاوی داده های درخواستی به عنوان DOMString ، ArrayBuffer ، Blob یا Document خواهد بود (بسته به آنچه برای responseType تنظیم شده است.)

با این شگفت‌انگیز جدید، می‌توانیم مثال قبلی را دوباره کار کنیم، اما این بار، تصویر را به‌جای رشته، به صورت Blob واکشی کنیم:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    // Note: .response instead of .responseText
    var blob = new Blob([this.response], {type: 'image/png'});
    ...
  }
};

xhr.send();

خیلی قشنگتره!

پاسخ های ArrayBuffer

ArrayBuffer یک ظرف با طول ثابت عمومی برای داده های باینری است. اگر به یک بافر کلی از داده‌های خام نیاز دارید، بسیار مفید هستند، اما قدرت واقعی پشت این افراد این است که می‌توانید «نما» داده‌های زیربنایی را با استفاده از آرایه‌های تایپ شده جاوا اسکریپت ایجاد کنید. در واقع، چندین نما را می توان از یک منبع ArrayBuffer ایجاد کرد. به عنوان مثال، می توانید یک آرایه عدد صحیح 8 بیتی ایجاد کنید که همان ArrayBuffer را با یک آرایه عدد صحیح 32 بیتی موجود از همان داده ها به اشتراک می گذارد. داده های اساسی یکسان باقی می مانند، ما فقط نمایش های متفاوتی از آن ایجاد می کنیم.

به عنوان مثال، تصویر زیر همان تصویر ما را به عنوان یک ArrayBuffer واکشی می کند، اما این بار، یک آرایه عدد صحیح 8 بیتی بدون علامت از آن بافر داده ایجاد می کند:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
  var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer
  // var byte3 = uInt8Array[4]; // byte at offset 4
  ...
};

xhr.send();

پاسخ های لکه ای

اگر می خواهید مستقیماً با Blob کار کنید و/یا نیازی به دستکاری هیچ یک از بایت های فایل ندارید، از xhr.responseType='blob' استفاده کنید:

window.URL = window.URL || window.webkitURL;  // Take care of vendor prefixes.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    var blob = this.response;

    var img = document.createElement('img');
    img.onload = function(e) {
      window.URL.revokeObjectURL(img.src); // Clean up after yourself.
    };
    img.src = window.URL.createObjectURL(blob);
    document.body.appendChild(img);
    ...
  }
};

xhr.send();

Blob را می توان در مکان های مختلفی استفاده کرد، از جمله ذخیره آن در indexedDB ، نوشتن آن در سیستم فایل HTML5، یا ایجاد URL Blob ، همانطور که در این مثال مشاهده می شود.

ارسال داده

توانایی دانلود داده ها در فرمت های مختلف عالی است، اما اگر نتوانیم این فرمت های غنی را به پایگاه اصلی (سرور) برگردانیم، ما را به جایی نمی رساند. XMLHttpRequest ما را برای مدتی محدود به ارسال داده های DOMString یا Document (XML) کرده است. دیگر نه. یک متد send() اصلاح‌شده برای پذیرش هر یک از انواع زیر لغو شده است: DOMString ، Document ، FormData ، Blob ، File ، ArrayBuffer . مثال‌های موجود در ادامه این بخش ارسال داده‌ها را با استفاده از هر نوع نشان می‌دهند.

ارسال داده رشته: xhr.send(DOMString)

function sendText(txt) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) {
    if (this.status == 200) {
      console.log(this.responseText);
    }
  };

  xhr.send(txt);
}

sendText('test string');
function sendTextNew(txt) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.responseType = 'text';
  xhr.onload = function(e) {
    if (this.status == 200) {
      console.log(this.response);
    }
  };
  xhr.send(txt);
}

sendTextNew('test string');

هیچ چیز جدیدی در اینجا وجود ندارد، اگرچه قطعه سمت راست کمی متفاوت است. responseType='text' برای مقایسه تنظیم می کند. باز هم حذف آن خط همان نتایج را به همراه دارد.

ارسال فرم ها: xhr.send(FormData)

احتمالاً بسیاری از مردم به استفاده از پلاگین های jQuery یا کتابخانه های دیگر برای رسیدگی به فرم های ارسالی AJAX عادت دارند. در عوض، می‌توانیم از FormData ، نوع داده جدید دیگری که برای XHR2 طراحی شده است، استفاده کنیم. FormData برای ایجاد یک <form> HTML در حال پرواز، در جاوا اسکریپت راحت است. سپس می توان آن فرم را با استفاده از AJAX ارسال کرد:

function sendForm() {
  var formData = new FormData();
  formData.append('username', 'johndoe');
  formData.append('id', 123456);

  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);
}

اساساً، ما فقط به صورت پویا یک <form> ایجاد می‌کنیم و با فراخوانی متد append، مقادیر <input> را روی آن قرار می‌دهیم.

البته نیازی به ایجاد <form> از ابتدا ندارید. اشیاء FormData را می توان از HTMLFormElement موجود در صفحه مقداردهی کرد. مثلا:

<form id="myform" name="myform" action="/server">
  <input type="text" name="username" value="johndoe">
  <input type="number" name="id" value="123456">
  <input type="submit" onclick="return sendForm(this.form);">
</form>
function sendForm(form) {
  var formData = new FormData(form);

  formData.append('secret_token', '1234567890'); // Append extra data before send.

  var xhr = new XMLHttpRequest();
  xhr.open('POST', form.action, true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);

  return false; // Prevent page from submitting.
}

یک فرم HTML می تواند شامل آپلود فایل باشد (به عنوان مثال <input type="file"> ) و FormData نیز می تواند آن را مدیریت کند. به سادگی فایل(ها) را ضمیمه کنید و وقتی send() فراخوانی شود، مرورگر یک درخواست multipart/form-data ایجاد می کند:

function uploadFiles(url, files) {
  var formData = new FormData();

  for (var i = 0, file; file = files[i]; ++i) {
    formData.append(file.name, file);
  }

  var xhr = new XMLHttpRequest();
  xhr.open('POST', url, true);
  xhr.onload = function(e) { ... };

  xhr.send(formData);  // multipart/form-data
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  uploadFiles('/server', this.files);
}, false);

بارگذاری یک فایل یا حباب: xhr.send(Blob)

همچنین می توانیم داده های File یا Blob را با استفاده از XHR ارسال کنیم. به خاطر داشته باشید که همه File Blob هستند، بنابراین هر کدام در اینجا کار می‌کنند.

این مثال یک فایل متنی جدید از ابتدا با استفاده از سازنده Blob() ایجاد می کند و آن Blob را در سرور آپلود می کند. کد همچنین یک کنترل کننده برای اطلاع کاربر از پیشرفت آپلود تنظیم می کند:

<progress min="0" max="100" value="0">0% complete</progress>
function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  // Listen to the upload progress.
  var progressBar = document.querySelector('progress');
  xhr.upload.onprogress = function(e) {
    if (e.lengthComputable) {
      progressBar.value = (e.loaded / e.total) * 100;
      progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
    }
  };

  xhr.send(blobOrFile);
}

upload(new Blob(['hello world'], {type: 'text/plain'}));

آپلود یک تکه بایت: xhr.send(ArrayBuffer)

آخرین اما نه کم‌اهمیت، ما می‌توانیم ArrayBuffer s را به عنوان محموله XHR ارسال کنیم.

function sendArrayBuffer() {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  var uInt8Array = new Uint8Array([1, 2, 3]);

  xhr.send(uInt8Array.buffer);
}

اشتراک‌گذاری منابع متقاطع (CORS)

CORS به برنامه های کاربردی وب در یک دامنه اجازه می دهد تا درخواست های بین دامنه ای AJAX را به دامنه دیگر ارسال کنند. فعال کردن آن بسیار ساده است، فقط باید یک سرصفحه پاسخ توسط سرور ارسال شود.

فعال کردن درخواست های CORS

فرض کنید برنامه شما در example.com زندگی می کند و می خواهید داده ها را از www.example2.com بیرون بکشید. معمولاً اگر سعی می‌کردید این نوع تماس AJAX را برقرار کنید، درخواست با شکست مواجه می‌شود و مرورگر خطای عدم تطابق مبدا را نشان می‌دهد. با CORS، www.example2.com می‌تواند به سادگی با افزودن یک هدر، درخواست‌های example.com را مجاز کند:

Access-Control-Allow-Origin: http://example.com

Access-Control-Allow-Origin می توان به یک منبع واحد در یک سایت یا در کل دامنه اضافه کرد. برای اینکه به هر دامنه ای اجازه دهید از شما درخواست کند، تنظیم کنید:

Access-Control-Allow-Origin: *

در واقع این سایت (html5rocks.com) CORS را در تمام صفحات خود فعال کرده است. Developer Tools را فعال کنید و Access-Control-Allow-Origin در پاسخ ما خواهید دید:

هدر Access-Control-Allow-Origin در html5rocks.com
هدر «Access-Control-Allow-Origin» در html5rocks.com

فعال کردن درخواست‌های متقاطع آسان است، بنابراین لطفاً، لطفاً اگر داده‌های شما عمومی است ، CORS را فعال کنید !

ایجاد یک درخواست متقابل دامنه

اگر نقطه پایانی سرور CORS را فعال کرده باشد، ایجاد درخواست متقاطع با یک درخواست معمولی XMLHttpRequest تفاوتی ندارد. برای مثال، در اینجا درخواستی است که example.com اکنون می تواند به www.example2.com بفرستد:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example2.com/hello.json');
xhr.onload = function(e) {
  var data = JSON.parse(this.response);
  ...
}
xhr.send();

نمونه های عملی

دانلود + ذخیره فایل ها در سیستم فایل HTML5

فرض کنید یک گالری تصاویر دارید و می خواهید تعدادی عکس را واکشی کنید، سپس آنها را به صورت محلی با استفاده از سیستم فایل HTML5 ذخیره کنید. یکی از راه های انجام این کار درخواست تصاویر به صورت Blob و نوشتن آنها با استفاده از FileWriter است:

window.requestFileSystem  = window.requestFileSystem || window.webkitRequestFileSystem;

function onError(e) {
  console.log('Error', e);
}

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {

  window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
    fs.root.getFile('image.png', {create: true}, function(fileEntry) {
      fileEntry.createWriter(function(writer) {

        writer.onwrite = function(e) { ... };
        writer.onerror = function(e) { ... };

        var blob = new Blob([xhr.response], {type: 'image/png'});

        writer.write(blob);

      }, onError);
    }, onError);
  }, onError);
};

xhr.send();

برش یک فایل و آپلود هر بخش

با استفاده از File API ها ، می توانیم کار برای آپلود یک فایل بزرگ را به حداقل برسانیم. تکنیک این است که آپلود را به چند تکه تقسیم کنید، برای هر قسمت یک XHR ​​ایجاد کنید و فایل را روی سرور قرار دهید. این شبیه به نحوه آپلود سریع پیوست های بزرگ توسط GMail است. همچنین می‌توان از چنین تکنیکی برای دور زدن محدودیت درخواست http 32 مگابایتی Google App Engine استفاده کرد.

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

})();

آنچه در اینجا نشان داده نشده است، کد بازسازی فایل در سرور است.

منابع