مقدمة
من بين الأبطال المجهولين في عالم HTML5، XMLHttpRequest
.
من الناحية الدقيقة، لا يُعدّ XHR2 من تنسيق HTML5. ومع ذلك، يُعدّ ذلك جزءًا من التحسينات المتزايدة التي يُجريها مورّدو المتصفّحات على المنصة الأساسية. سأضيف XHR2 إلى مجموعة
الميزات الجديدة لأنّه يلعب دورًا أساسيًا في تطبيقات الويب المعقّدة الحالية.
لقد أجرينا تغييرات كبيرة على هذه الميزة المألوفة، ولكن لا يعرف الكثير من المستخدمين ميزاتها الجديدة. XMLHttpRequest من المستوى 2: يقدّم XMLHttpRequest من المستوى 2 مجموعة كبيرة من الإمكانات الجديدة التي تضع حدًا للاختراقات المعقّدة في تطبيقات الويب، ويشمل ذلك طلبات من مصادر متعددة وأحداث تحميل التقدّم وإمكانية تحميل/تنزيل البيانات الثنائية. تسمح هذه التقنيات لواجهة AJAX بالعمل مع العديد من واجهات برمجة تطبيقات 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.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
و/أو
لم تكن بحاجة إلى التلاعب بأي من وحدات البايت في الملف، استخدِم 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 أثناء التشغيل، في 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);
تحميل ملف أو ملف نصي: xhr.send(Blob)
يمكننا أيضًا إرسال بيانات File
أو Blob
باستخدام طلبات HTTP المتعدّدة.
يُرجى العِلم أنّ جميع File
هي Blob
، لذا يمكن استخدام أيّ منهما هنا.
ينشئ هذا المثال ملفًا نصيًا جديدًا من البداية باستخدام Blob()
constructor
ويحمّل هذا 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 كحمولة طلب البيانات عبر HTTP.
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();
تقسيم ملف وتحميل كل جزء
باستخدام File APIs، يمكننا تقليل المجهود المبذول لتحميل ملف كبير. وتتم هذه العملية من خلال تقسيم عملية التحميل إلى أجزاء متعددة، وإنشاء طلب 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