مقدمة
"XMLHttpRequest
" هو أحد الأبطال المجهولين في عالم HTML5.
باختصار، XHR2 ليس HTML5. ومع ذلك، يعد جزءًا من التحسينات الإضافية التي
يجريها موردو المتصفحات على النظام الأساسي الأساسي. أقوم بتضمين XHR2 في حقيبة الهدايا الجديدة
لأنها تلعب دورًا لا يتجزأ في تطبيقات الويب المعقدة اليوم.
تبين أن الصديق القديم تعرض عملية تطوير كبيرة لكن الكثير من الأشخاص لا يعلمون بميزاته الجديدة. يقدّم XMLHttpRequest المستوى 2 مجموعة كبيرة من الإمكانات الجديدة التي تساعد في إنهاء عمليات الاختراق المعقّدة في تطبيقات الويب، مثل الطلبات المشتركة المصدر، وتحميل أحداث التقدّم، وإمكانية تحميل/تنزيل البيانات الثنائية. وهي تسمح لتطبيق AJAX بالعمل مع العديد من واجهات برمجة تطبيقات HTML5 المتطورة مثل واجهة برمجة تطبيقات نظام الملفات وواجهة برمجة تطبيقات Web Audio و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.responseType
- قبل إرسال طلب، اضبط
xhr.responseType
على "نص" أو "صفيف احتياطي" أو "فقاعة" أو "مستند"، حسب احتياجات البيانات. ملاحظة: سيؤدي ضبطxhr.responseType = ''
(أو حذف) إلى ضبط الرد تلقائيًا على "نص". - xhr.response
- بعد تقديم طلب ناجح، ستحتوي سمة استجابة 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
هي حاوية عامة ذات طول ثابت للبيانات الثنائية. وهي مفيدة للغاية إذا كنت بحاجة إلى مخزن مؤقت عام للبيانات الأولية، ولكن الميزة الحقيقية وراء هذه الأدوات هي أنه يمكنك إنشاء "طرق عرض" للبيانات الأساسية باستخدام الصفائف المكتوبة بلغة JavaScript.
في الواقع، يمكن إنشاء ملفات شخصية متعددة من مصدر 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
إذا كنت تريد العمل مباشرةً على 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
سهل لإنشاء ملف HTML <form>
بشكل آني وفي JavaScript.
يمكن بعد ذلك إرسال هذا النموذج باستخدام 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>
ديناميكيًا ونضيف
قيم <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
معالجة ذلك أيضًا. ما عليك سوى إلحاق الملف(الملفات) وسينشئ المتصفّح
طلب multipart/form-data
عند استدعاء send()
:
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);
تحميل ملف أو كائن blob: 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
كحمولة 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 على جميع صفحاته. شغّل أدوات المطورين، وسيظهر لك Access-Control-Allow-Origin
في ردنا:
من السهل تفعيل طلبات مشاركة المحتوى من مصادر متعددة، لذا يُرجى تفعيل سياسة 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();
تقسيم ملف وتحميل كل جزء
باستخدام واجهات برمجة تطبيقات الملفات، يمكننا تقليل العمل اللازم لتحميل ملف كبير. ويتمثل الأسلوب في تقسيم التحميل إلى أجزاء متعددة، وإنتاج 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);
})();
ما لا يظهر هنا هو التعليمة البرمجية لإعادة إنشاء الملف على الخادم.
المراجع
- مواصفات XMLHttpRequest المستوى 2
- مواصفات مشاركة الموارد المتعدّدة المصادر (CORS)
- مواصفات File API
- مواصفات FileSystem API