Web前端文件上传进度的显示
跟后台关系不大,主要是前端js实现,具体使用了XMLHttpRequest的ProgressEvent事件,可以参考MDN中的Using XMLHttpRequest
https://developer.mozilla.org/cn/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Submitting_forms_and_uploading_files
XMLHttpRequest
让发送一个HTTP请求变得非常容易。你只需要简单的创建一个请求对象实例,打开一个URL,然后发送这个请求。当传输完毕后,结果的HTTP状态以及返回的响应内容也可以从请求对象中获取。本页把这个强大的JavaScript对象的一些常用的甚至略有晦涩的使用案例进行了一下概述。
function reqListener () {
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.open("get", "yourFile.txt", true);
oReq.send();
请求类型Edit
通过XMLHttpRequest生成的请求可以有两种方式来获取数据,异步模式或同步模式。请求的类型是由这个XMLHttpRequest对象的open()方法的第三个参数async
的值决定的。如果该参数的值为false,则该XMLHttpRequest请求以同步模式进行,否则该过程将以异步模式完成。这两种类型请求的详细讨论和指南可以在同步和异步请求页找到。
注意:由于对用户体验的糟糕效果,从Gecko 30.0(Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27)版本开始,在主线程上的同步请求已经被弃用。
处理响应Edit
W3C规范定义了XMLHttpRequest对象的几种类型的响应属性。这些属性告诉客户端关于XMLHttpRequest返回状态的重要信息。一些处理非文本返回类型的用例可能包含一些下面章节描述的操作和分析。
分析并操作 responseXML属性
如果你使用 XMLHttpRequest 来获得一个远程的 XML 文档的内容,responseXML 属性将会是一个由 XML 文档解析而来的 DOM 对象,这很难被操作和分析。这里有五种主要的分析 XML 文档的方式:
- 使用 XPath 定位到文档的制定部分。
- 使用 JXON 将其转换成 JavaScript 对象树。
- 手工的 解析和序列化 XML 为字符串或对象。
- 使用 XMLSerializer 把 DOM 树序列化成字符串或文件。
- 如果你预先知道 XML 文档的内容,你可以使用 RegExp。如果你用 RegExp 扫描时受到换行符的影响,你也许想要删除所有的换行符。然而,这种方法是"最后手段",因为如果 XML 代码发生轻微变化,该方法将可能失败。
解析和操作包含 HTML 文档的 responseText
属性
如果使用 XMLHttpRequest
从远端获取一个 HTML 页面,则所有 HTML 标记会以字符串的形式存放在responseText 属性里,这样就使得操作和解析这些标记变得困难。解析这些HTML标记主要有三种方式:
- 使用 XMLHttpRequest.responseXML 属性。
- 将内容通过 fragment.body.innerHTML 注入到一个 文档片段 中,并遍历 DOM 中的片段。
- 如果你预先知道 HTML 文档的内容,你可以使用 RegExp 。如果你用 RegExp 扫描时受到换行符的影响,你也许想要删除所有的换行符。 然而,这种方法是"最后手段",因为如果 HTML 代码发生轻微变化,该方法将可能失败。
处理二进制数据Edit
尽管 XMLHttpRequest
一般用来发送和接收文本数据,但其实也可以发送和接受二进制内容。有许多经过良好测试的方法来强制使用 XMLHttpRequest 发送二进制数据。利用 XMLHttpRequest 的 .overrideMimeType() 方法是一个解决方案,虽然它并不是一个标准方法。
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
// retrieve data unprocessed as a binary string
oReq.overrideMimeType("text/plain; charset=x-user-defined");
/* ... */
在 XMLHttpRequest Level 2 规范中新加入了 responseType 属性 ,使得发送和接收二进制数据变得更加容易。
var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
var arraybuffer = xhr.response; // not responseText
/* ... */
}
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.send();
更多示例请参考 发送和接收二进制数据 。
监测进度Edit
XMLHttpRequest
提供了各种在请求被处理期间发生的事件以供监听。这包括定期进度通知、 错误通知,等等。
支持 DOM 的 progress 事件监测之于 XMLHttpRequest
传输,遵循 Web API 进度事件规范 : 这些事件实现了 ProgressEvent
接口。
var req = new XMLHttpRequest();
req.addEventListener("progress", updateProgress, false);
req.addEventListener("load", transferComplete, false);
req.addEventListener("error", transferFailed, false);
req.addEventListener("abort", transferCanceled, false);
req.open();
...
// progress on transfers from the server to the client (downloads)
function updateProgress(evt) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
...
} else {
// Unable to compute progress information since the total size is unknown
}
}
function transferComplete(evt) {
alert("The transfer is complete.");
}
function transferFailed(evt) {
alert("An error occurred while transferring the file.");
}
function transferCanceled(evt) {
alert("The transfer has been canceled by the user.");
}
第 3-6 行为多种事件添加了事件监听,这些事件在使用 XMLHttpRequest
执行数据传输时被发出。
open()
之前添加事件监听。否则 progress 事件将不会被触发。在上个例子中,progress 事件被指定由 updateProgress()
函数处理,并接收到传输的总字节数和已经传输的字节数,它们分别在事件对象的 total
和 loaded
属性里。但是如果 lengthComputable
属性的值是 false,那么意味着总字节数是未知并且 total 的值为零。
progress 事件同时存在于下载和上传的传输。下载相关事件在 XMLHttpRequest
对象上被触发,就像上面的例子一样。上传相关事件在 XMLHttpRequest.upload
对象上被触发,像下面这样:
var req = new XMLHttpRequest();
req.upload.addEventListener("progress", updateProgress);
req.upload.addEventListener("load", transferComplete);
req.upload.addEventListener("error", transferFailed);
req.upload.addEventListener("abort", transferCanceled);
req.open();
file:
协议的情况下是无效的。responseType
为 "moz-blob" 时,如果你的 progress 事件被触发,则响应的值是一个包含了接收到的数据的 Blob
。使用 loadend
事件可以侦测到所有的三种加载结束条件(abort、load、error):
req.addEventListener("loadend", loadEnd, false);
function loadEnd(evt) {
alert("The transfer finished (although we don't know if it succeeded or not).");
}
需要注意的是,没有方法可以确切的知道 loadend
事件接收到的信息是来自何种条件引起的操作终止;但是你可以在所有传输结束的时候使用这个事件处理。
提交表单和上传文件Edit
XMLHttpRequest
的实例有两种方式提交表单:
- 使用 AJAX
- 使用
FormData
API
第二种方式( 使用 FormData
API )是最简单最快捷的,但是缺点是被收集的数据不是字符串形式的。
第一种方式反而是最复杂的但也是最灵活和最强大。
只使用 XMLHttpRequest
在大多数用例中,提交表单时即便不使用 FormData
API 也不会要求其他的 API。唯一的例外情况是,如果你要上传一个或多个文件,你需要额外的 FileReader
API。
提交方法简介
一个 html <form>
可以用以下四种方式发送:
- 使用
POST
方法,并设置enctype
属性为application/x-www-form-urlencoded
(默认) - 使用
POST
方法,并设置enctype
属性为text/plain
- 使用
POST
方法,并设置enctype
属性为multipart/form-data
- 使用
GET
方法(这种情况下enctype
属性会被忽略)
现在,我们提交一个表单,它里面有两个字段,分别被命名为 foo
和 baz
。如果你用 POST
方法,那么服务器将会接收到一个字符串类似于下面三种情况之一,其中的区别依赖于你采用何种编码类型:
- 方法:
POST
; - 编码类型:
application/x-www-form-urlencoded
(default):
Content-Type: application/x-www-form-urlencoded
foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A
- 方法:
POST
; - 编码类型:
text/plain
:
Content-Type: text/plain
foo=bar
baz=The first line.
The second line.
- 方法:
POST
; - 编码类型:
multipart/form-data
:
Content-Type: multipart/form-data; boundary=---------------------------314911788813839
-----------------------------314911788813839
Content-Disposition: form-data; name="foo"
bar
-----------------------------314911788813839
Content-Disposition: form-data; name="baz"
The first line.
The second line.
-----------------------------314911788813839--
相反的,如果你用 GET
方法,一个像下面这样的字符串将被简单的附加到 URL:
?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.
一个小的普通的框架
所有这些事情都是由浏览器在你提交一个 <form>
的时候自动完成的。但是如果你想要用 JavaScript 做同样的事情,你不得不告诉解释器所有的事。那么,如何发送表单这件事在使用纯粹的 AJAX 时会复杂到无法在这里解释清楚。基于这个原因,我们提供一个完整的(但仍然教条的)框架,它可以使用所有的四种提交方式,甚至上传文件:
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Sending forms with pure AJAX – MDN</title>
<script type="text/javascript">
"use strict";
/*\
|*|
|*| :: XMLHttpRequest.prototype.sendAsBinary() Polyfill ::
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#sendAsBinary()
\*/
if (!XMLHttpRequest.prototype.sendAsBinary) {
XMLHttpRequest.prototype.sendAsBinary = function(sData) {
var nBytes = sData.length, ui8Data = new Uint8Array(nBytes);
for (var nIdx = 0; nIdx < nBytes; nIdx++) {
ui8Data[nIdx] = sData.charCodeAt(nIdx) & 0xff;
}
/* send as ArrayBufferView...: */
this.send(ui8Data);
/* ...or as ArrayBuffer (legacy)...: this.send(ui8Data.buffer); */
};
}
/*\
|*|
|*| :: AJAX Form Submit Framework ::
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest
|*|
|*| This framework is released under the GNU Public License, version 3 or later.
|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*| Syntax:
|*|
|*| AJAXSubmit(HTMLFormElement);
\*/
var AJAXSubmit = (function () {
function ajaxSuccess () {
/* console.log("AJAXSubmit - Success!"); */
alert(this.responseText);
/* you can get the serialized data through the "submittedData" custom property: */
/* alert(JSON.stringify(this.submittedData)); */
}
function submitData (oData) {
/* the AJAX request... */
var oAjaxReq = new XMLHttpRequest();
oAjaxReq.submittedData = oData;
oAjaxReq.onload = ajaxSuccess;
if (oData.technique === 0) {
/* method is GET */
oAjaxReq.open("get", oData.receiver.replace(/(?:\?.*)?$/, oData.segments.length > 0 ? "?" + oData.segments.join("&") : ""), true);
oAjaxReq.send(null);
} else {
/* method is POST */
oAjaxReq.open("post", oData.receiver, true);
if (oData.technique === 3) {
/* enctype is multipart/form-data */
var sBoundary = "---------------------------" + Date.now().toString(16);
oAjaxReq.setRequestHeader("Content-Type", "multipart\/form-data; boundary=" + sBoundary);
oAjaxReq.sendAsBinary("--" + sBoundary + "\r\n"