เกริ่นนำ
หนึ่งในฮีโร่ที่ยังไม่ได้ร้องในจักรวาล HTML5 คือ XMLHttpRequest
พูดง่ายๆ ก็คือ XHR2 ไม่ใช่ HTML5 แต่นี่เป็นส่วนหนึ่งของการปรับปรุงเพิ่มเติมที่ผู้ให้บริการเบราว์เซอร์ทำกับแพลตฟอร์มหลัก ผมได้รวม XHR2 ไว้ในกระเป๋าสารพัดอันใหม่ของเรา
เพราะว่าแอปนี้มีบทบาทสำคัญมากในเว็บแอปที่ซับซ้อนในปัจจุบัน
ปรากฏว่าเพื่อนเก่าของเรามีการแปลงโฉมครั้งใหญ่ แต่หลายๆ คนยังไม่ทราบเกี่ยวกับฟีเจอร์ใหม่ XMLHttpRequest ระดับ 2 เปิดตัวความสามารถใหม่ๆ มากมายเพื่อขจัดการแฮ็กที่ซับซ้อนในเว็บแอปของเรา สิ่งต่างๆ เช่น คำขอข้ามต้นทาง การอัปโหลดเหตุการณ์ความคืบหน้า และการรองรับการอัปโหลด/ดาวน์โหลดข้อมูลไบนารี ซึ่งช่วยให้ AJAX ทำงานร่วมกับ API ของ HTML5 แบบ Bleed Edge ได้หลายรายการ เช่น File System API, Web Audio API และ WebGL
บทแนะนำนี้จะไฮไลต์ฟีเจอร์ใหม่บางส่วนใน XMLHttpRequest
โดยเฉพาะฟีเจอร์ที่ใช้สำหรับการทำงานกับไฟล์
กำลังดึงข้อมูล
การดึงข้อมูลไฟล์เป็น Blob ไบนารีสร้างความยุ่งยากเมื่อใช้ 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
ไม่ใช่ไบนารี BLOB โดยเป็นสตริงไบนารีที่แสดงไฟล์ภาพ
เรากำลังหลอกให้เซิร์ฟเวอร์ส่งข้อมูลกลับไปโดยไม่ได้รับการประมวลผล
แม้ว่าอัญมณีชิ้นเล็กๆ นี้จะใช้ได้ผล แต่ฉันขอเรียกสิ่งนี้ว่าเวทมนตร์ดำ
และแนะนำให้รู้จักกัน เมื่อใดก็ตามที่คุณต้องการแฮ็กโค้ดอักขระและการปรับแต่งสตริง
เพื่อบังคับให้ข้อมูลอยู่ในรูปแบบที่คุณต้องการ นี่อาจเป็นปัญหาจริงๆ
การระบุรูปแบบการตอบสนอง
ในตัวอย่างก่อนหน้านี้ เราดาวน์โหลดอิมเมจเป็น "ไฟล์" ไบนารีโดยการลบล้างประเภท MIME ของเซิร์ฟเวอร์และประมวลผลข้อความตอบกลับเป็นสตริงไบนารี
แต่จะใช้ประโยชน์จากพร็อพเพอร์ตี้ responseType
และ response
ใหม่ของ XMLHttpRequest
แทนเพื่อบอกให้เบราว์เซอร์ทราบว่าคุณต้องการให้ส่งข้อมูลในรูปแบบใด
- xhr.responseType
- ก่อนส่งคำขอ ให้ตั้งค่า
xhr.responseType
เป็น "text", "arraybuffer", "blob" หรือ "document" ทั้งนี้ขึ้นอยู่กับความต้องการด้านข้อมูล โปรดทราบว่าการตั้งค่า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();
ตัดไฟล์และอัปโหลดแต่ละส่วน
การใช้ API ไฟล์ช่วยให้เราลดเวลาในการอัปโหลดไฟล์ขนาดใหญ่ได้ เทคนิคคือแบ่งการอัปโหลดออกเป็นหลายๆ ส่วน สร้าง XHR สำหรับแต่ละส่วน แล้วรวมไฟล์ไว้ด้วยกันบนเซิร์ฟเวอร์ ซึ่งคล้ายกับวิธีที่ Gmail อัปโหลดไฟล์แนบขนาดใหญ่ได้อย่างรวดเร็ว เทคนิคดังกล่าวอาจช่วยหลีกเลี่ยงขีดจำกัดคำขอ http ที่ 32 MB ของ 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