轉:XMLHttpRequest2 新技巧

    」XMLHttpRequest 的異步調用網上找的例子運行沒問題,但稍微改了一點點就報錯」InvalidStateError: XMLHttpRequest has an invalid context「。斷斷續續 搞了3天終於通了,能夠接收二進制文件了。 「 以後找到了下篇文章,發現我所使用的方法是下方中的老方法。準備再按下文所說新方法試試。php

    下文是2011寫的了,想必所說內容到如今已經是全部瀏覽器都支持的了吧。html

 

如下轉自:  https://www.html5rocks.com/zh/tutorials/file/xhr2/。html5

 

 

XMLHttpRequest2 新技巧

HTML5 Rocksjquery

目錄

 

Eric Bidelman

By Eric Bidelmanweb

已發佈: 五月 27th, 2011 Comments: 0apache

簡介

HTML5 世界中有這樣一位無名英雄:XMLHttpRequest。嚴格地說,XHR2 並不屬於 HTML5。不過,它是瀏覽器供應商對於核心平臺不斷作出的改進中的一部分。我之因此將 XHR2 加入咱們新的百寶囊中,就是由於它在現在複雜的網絡應用中扮演了不可或缺的角色。json

結果呢,咱們這位老朋友來了個大變身,不少人都不知道它的新功能了。2 級 XMLHttpRequest 引入了大量的新功能(例如跨源請求、上傳進度事件以及對上傳/下載二進制數據的支持等),一舉封殺了咱們網絡應用中的瘋狂黑客。這使得 AJAX 能夠與不少尖端的 HTML5 API 結合使用,例如 File System APIWeb Audio API 和 WebGL。api

此教程重點介紹 XMLHttpRequest 中的新功能,尤爲是可用於處理文件的功能。跨域

抓取數據

之前經過 XHR 抓取二進制 blob 形式的文件是很痛苦的事情。從技術上來講,這甚至是不可能的實現。有一種廣爲流傳的一種技巧,是將 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 類型並將響應文本做爲二進制字符串處理,下載了二進制「文件」形式的圖片。如今,讓咱們利用 XMLHttpRequest 新增的 responseTyperesponse 屬性,告知瀏覽器咱們但願返回什麼格式的數據。

xhr.responseType

在發送請求前,根據您的數據須要,將 xhr.responseType 設置爲「text」、「arraybuffer」、「blob」或「document」。請注意,設置(或忽略)xhr.responseType = '' 會默認將響應設爲「text」。

xhr.response

成功發送請求後,xhr 的響應屬性會包含 DOMStringArrayBufferBlobDocument 形式(具體取決於 responseTyp 的設置)的請求數據。

憑藉這個優秀的新屬性,咱們能夠修改上一個示例:以 ArrayBuffer 而非字符串的形式抓取圖片。將緩衝區移交給 BlobBuilder API 可建立 Blob

BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

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

xhr.onload = function(e) {
  if (this.status == 200) {
    var bb = new BlobBuilder();
    bb.append(this.response); // Note: not xhr.responseText

    var blob = bb.getBlob('image/png');
    ...
  }
};

xhr.send();

好多了!

ArrayBuffer 響應

ArrayBuffer 是二進制數據通用的固定長度容器。若是您須要原始數據的通用緩衝區,ArrayBuffer 就很是好用,可是它真正強大的功能是讓您使用 JavaScript 類型數組建立底層數據的「視圖」。實際上,能夠經過單個 ArrayBuffer 來源建立多個視圖。例如,您能夠建立一個 8 位整數數組,與來自相同數據的現有 32 位整數數組共享同一個 ArrayBuffer。底層數據保持不變,咱們只是建立其不一樣的表示方法。

例如,下面以 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 文件系統建立 Blob 網址(如本例中所示)。

發送數據

可以下載各類格式的數據當然是件好事,可是若是不能將這些豐富格式的數據送回本壘(服務器),那就毫無心義了。XMLHttpRequest 有時候會限制咱們發送 DOMStringDocument (XML) 數據。可是如今不會了。現已替換成通過修改的 send() 方法,可接受如下任何類型:DOMStringDocumentFormDataBlobFileArrayBuffer。本部分的其他內容中的示例演示瞭如何使用各種型發送數據。

發送字符串數據: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);
}

sendText2('test string');

這沒有新內容,只是正確的代碼段略有不一樣。其中設置了 responseType='text' 做爲對比。再次說明,省略此行會獲得一樣的結果。

提交表單:xhr.send(FormData)

不少人可能習慣於使用 jQuery 插件或其餘庫來處理 AJAX 表單提交。而咱們能夠改用 FormData,這是另外一種針對 XHR2 設計的新數據類型。使用 FormData 可以很方便地實時以 JavaScript 建立 HTML <form>。而後可使用 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);

上傳文件或 blob:xhr.send(Blob)

咱們也可使用 XHR 發送 FileBlob。請注意,全部 File 都是 Blob,因此在此使用二者皆可。

該示例使用 BlobBuilder API 從頭開始建立新的文本文件,並將該 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);
}

// Take care of vendor prefixes.
BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

var bb = new BlobBuilder();
bb.append('hello world');

upload(bb.getBlob('text/plain'));

上傳字節:xhr.send(ArrayBuffer)

最後也是至關重要的一點就是,咱們能以 XHR 的有效負載形式發送 ArrayBuffer

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 了:

Access-Control-Allow-Origin header on html5rocks.com

html5rocks.com 上的 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 文件系統本地保存這些圖片。一種方法是以 ArrayBuffer 形式請求圖片,經過數據構建 Blob,並使用 FileWriter 寫入 blob:

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 = 'arraybuffer';

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 bb = new BlobBuilder();
        bb.append(xhr.response);

        writer.write(bb.getBlob('image/png'));

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

xhr.send();

請注意:要使用此代碼,請參閱「探索 FileSystem API」教程中的瀏覽器支持和存儲限制

分割文件並上傳各個部分

使用 File API,咱們能夠將操做簡化爲上傳大文件。咱們採用的技術是:將要上傳的文件分割成多個部分,爲每一個部分生成一個 XHR,而後在服務器上將各部分組合成文件。這相似於 Gmail 快速上傳大附件的方法。使用這種技術還能夠規避 Google 應用引擎對 http 請求的 32 MB 限制。

window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder ||
                     window.BlobBuilder;

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) {

    // Note: blob.slice has changed semantics and been prefixed. See http://goo.gl/U9mE5.
    if ('mozSlice' in blob) {
      var chunk = blob.mozSlice(start, end);
    } else {
      var chunk = blob.webkitSlice(start, end);
    }

    upload(chunk);

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

})();

用於在服務器上重組文件的代碼並未在此顯示。

趕快試試吧

#bytes/chunk:

參考

0

Next steps

Share

Twitter Facebook Google+

Subscribe

Enjoyed this article? Grab the RSS feed and stay up-to-date.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License.

相關文章
相關標籤/搜索