【js学习笔记-------HTTP脚本化】
通常,HTTP并不在脚本控制下,只是当用户单击链接,提交表单,和输入URL时才发生。但是,用js操作http是可行的,可以通过 window.location属性或调用 表单对象的submit()方法时,都会初始化http请求。在这两种情况下,浏览器会加载新页面。这种方式在多框架页面中非常有用。不过这不是本节要讨论的内容
AJAX:主要特点是脚本操纵http和web服务器来进行数据交换,不会导致页面重载。
Comet:是和AJAX相反。web服务器发起通信并异步发送消息到客户端。如果web应用需要响应服务端的消息,则它会使用ajax技术发送或请求数据。
AJAX是客户端人服务端“拉”数据。而Comet中,服务端向客户端“推”数据(服务器推,ajax推和http流)。
把<iframe>作为AJAX传输协议使用,脚本首先要把发送给web服务器的信息编码到url中,然后设置,iframe的src属性为该url。服务器能创建一个包含响应内容的html文档,并把它返回给web浏览器,并且在iframe中显示它,需要对用户不可见,例如可以使用css隐藏它。脚本能通过遍历<iframe>的文档对象来读取服务端的响应。这种方式受同源策略的影响。
<script>元素的src属性设置url并发起http get请求。使用<script>元素实现脚本操纵http是非常吸引人的,因为它们可以跨域通信而不受限于同源策略。通常,使用基于<script>的ajax传输协议时,服务器的响应采用json编码的数据格式,当执行脚本时,js解析器能自动将其“解码”。由于它使用json数据格式,因此这种ajax传输协议也叫做“JSONP”。
XMLHttpRequest对象,它定义了用脚本操纵http的API。除了常用的get请求,这个api还包含实现post请求的能力,同时它能用文本或Document对象的形式返回服务器响应。
Comet传输协议比AJAX更精妙,但都需要客户端和服务器之间建立连接,同时需要服务器保持连接处于打开状态,这样它才能够发送异步信息。隐藏的<iframe>能像Comet传输协议一样有用。例如,服务器以<iframe>中待执行的<script>元素的形式发送每条消息。实现Comet的一种更可靠平台方案是客户端建立一个和服务器的连接(使用ajax传输协议),同时服务器保持这个连接打开直到它需要摄推送一条消息。服务器每发送一条消息就关闭这个连接,这样可以确保客户端正确接收到消息。处理该消息之后,客户端马上为后续的消息推送建立一个新的连接。
在Ajax和Comet之上构建更高级的通信协议是可行的。例如这些客户端/服务器可以用做RPC机制或发布订阅事件系统的基础
使用XMLHttpRequest
这个类的每个实例都表示一个独立的请求/响应对,并且这个对象的属性和方法允许指定请求细节和提取响应数据。
W3C正在制订“2级XMLHttpRequest”标准草案。
使用:
var request = new XMLHttpRequest();
可以重用已存在的XMLHttpRequest,但注意这将会终止之前通过该对象挂起的任何请求
MS最早把XMLHttpRequest对象引入到IE5中,且在ie5和ie6中它只是一个ActiveX对象。IE7之前的版本不支持非标准的XMLHttpRequest()构造函数。但它能做如下的模拟
if (window.XMLHttpRequest == undefined) {window.XMLHttpRequest = function () {
try {
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
} catch (e1) {
try {
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
} catch (e2) {
throw new Error("XMLHttpRequestis not supported");
}
}
}
}
一个http请求由4部分组成
- http请求方法或“动作”
- 正在请求的url
- 一个可选的请求头集合,其中可能包括身份验证信息
- 一个可选的请求主体
服务器返回一个http响应包含3部分
- 一个数字和文字组成的状态码,用来显示请求的成功和失败
- 一个响应头集合
- 响应主体
指定请求
创建了XMLHttpRequest对象之后,发起http请求的下一步是调用XMLHttpRequest对象的open()方法去指定这个请求的两个必需部分:方法和url
open()的第一个参数指定http方法或动作。这个字符串不区分大小写,但通常大家用大写字母来匹配http协议。“GET”和”POST” 方法是得到广泛支持的。同时规范还支持 “DELETE”、 “HEAD”、 “OPTIONS”、 “PUT”
第二个参数是url,它是请求的主题,这是相对于文档的url,这个文档包含调用open()的脚本。如果指定绝对url、协议、主机和端口通常必须匹配所在文档的内容:跨域的请求通常会报错。
request.setRequestHeader(“Content-Type”,”text/plain”);
相同的头调用setRequestHeader()多次,新值不会取代指定的值,相反,http请求将包含这个头的多个副本或这个头将指定多个值。
用户自己不能指定 “Content-Length”、 “Date”、 “Referer”、 “User-Agent”头。XMLHttpRequest将自动添加这些头而防止伪造它们。同样,XMLHttpRequest对象自己处理cookie、连接时间、字符集和编码判断,所以你无法身setRequestHeader()传递这些头:
Accept-Charset | Content-Transfer-Encoeding | TE |
Accept-Encoeding | Date | Trailer |
Connection | Expect | Transfer-Encoding |
Content-Length | Host | Upgrade |
Cookie | Keep-Alive | User-Agent |
Cookie2 | Referer | Via |
XMLHttpRequest.send()这是请求的最后一步是指定可选的请求主体并向服务器发送它。
如:request.send(null); //GET 请求
GET请求绝对没有主体,所以应该传递null或省略这个参数。POST请求通常拥有主体同时它应该匹配使用setRequestHeader()指定的“Content-Type”头。
例:POST方法发送纯文本给服务器
fucntion postMessage(msg){
var request =new XMLHttpRequest(); //
request.open(“POST”,”/log.php”);
request.setRequestHeader(“Content-Type”,”text/plain;charset=UTF-8”);
request.send(msg); //将msg作为请求主体发送
//请求完成,我们将忽略任何响应和任何错误
}
取得响应
一个完整的http响应由状态码、响应头集合、和响应主体组成。这引起都可以通过XMLHttpReqeust对象的属性和方法使用:
l status和statusText属性以数字和文本的形式返回http状态码。如:200 404
l 使用getResponseHeader()和getAllResponseHeaders()能查询响应头。XMLHttpRequest会自动处理cookie:它会从getAllResponseHeaders()头返回集合中过滤掉的cookie头,而如果给getResponseHeader()传递”Set-Cookie”和”Set-Cookie2”则返回null
l 响应主体可以从responseText属性中得到文本形式,从responseXML属性中得到Document形式(它实际上对XHTML和XML文档有效)
监听readystatechange事件,可以用来处理响应完后需要触发的动作。
readyState是一个整数它指定了http请求的状态,同时列出了它可能的值:
常量 | 值 | 含义 |
UNSENT | 0 | open()尚未调用 |
OPENED | 1 | open()已调用 |
HEADERS_RECEIVED | 2 | 接收到头信息 |
LOADING | 3 | 接收到响应主体 |
DONE | 4 | 响应完成 |
理论上,每次readyState属性改变都会触发readystatechange。实际中,当readyState改变0或1时可能没有触发这个事件。当调用send()时,即使readyState仍处于OPEND状态,也通常触发它。某些浏览器在LOADING状态时,触发多次事件给出进度反馈。当readyState值改变为4或服务器的响应完成时,所有的浏览器都触发readystatechange事件。因为在响应完成之前也会触发事件,所以事件处事程序应该一直检验readyState值。为了监听readystatechange事件,可以把事件处理函数设置为XMLHttpRequest对象的onreadystatechange属性。也能使用addEventListener()或attachEvent(),但通常每个请求只需要一个处理程序,所以只设置onreadystatechange更容易
例 ://发出一个http get 请求以获得指定url的内容
//当响应成功到达,验证它是否是纯文本
//如果是,把它传递给指定回调函数
function getText(url,callback){
var request = new XMLHttpRequest();
request.open(“GET”,url);
request.onreadystatechange = function(){
if(request.readyState===4 &&request.status===200){
var type =request.getResponseHeader(“Content-Type”);
if(type.match(/^text/))
callback(request.responseText);
}
}
request.send(null);
}
同步响应
XMLHttpRequest对象支持同步响应。即open()的第三个参数设置为false。那么send()方法将阻塞直到请求完成。这种情况下,不需要使用事件处理程序:一旦send()返回,仅需要检查XMLHttpRequest对象的status和responseText属性如下:
function getTextSync(url){
var request = new XMLHttpRequest();
request.open(“GET”,url,false); //同步
request.send(null);
if(request.status!==200) throw newError(request.statusText);
var type =request.getRequestHeader(“Content-Type”);
if(!type.match(/^text/)) throw newError(‘Expected textual response;got:’+type);
return request.responseText;
}
响应解码
如果服务器发送xml或xhtml文档作为其响应,你能通过responseXML属性获得一个解析形式的xml文档。这个属性的值是一个Document对象,可以使用前面学到的技术搜索和遍历它。如果服务器想发送诸如对象或数组这样的结构化数据作为其响应,它应该传输JSON编码的字符串数据。当接收它时,可以把responseText属性传递给JSON.parse()。
如下:
//发起HTTP GET响应以获取指定URL的内容
//当响应到达时,把它以解析后的xml document对象、解析后的json对象
//或字符串的形式传递给回调函数
function getText(url,callback){
var request = new XMLHttpRequest();
request.open(“GET”,url);
request.onreadystatechange = function(){
if(request.readyState===4 &&request.status===200){
var type =request.getResponseHeader(“Content-Type”);
if(type.indexOf(“xml”)!==-1&& request.responseXML)
callback(request.responseXML);
else if(type===”application/json”)
callback(JSON.parse(request.responseText);
else
callback(request.responseText);
}
}
request.send(null);
}
你可以使用 XMLHttpRequest请求js脚本,然后使用全局eval()执行这个脚本。但,在这种情况下不需要使用XMLHttpRequest对象,因为<script>元素本身操纵http脚本的能力完全可以实现加载并执行脚本。且记住<script>元素能发起跨域HTTP请求,而XMLHttpRequest API则禁止。
web服务器通常使用二进制数据响应http请求,resposneText属性只能用于文本,且它不能妥善处理二进制响应,XHR2定义了处理二进制响应的方法,但厂商还没实现。
如果服务器在Content-Type 头中包含错误的 “charset”参数或是没有设置Content-Type。那么XMLHttpRequest将使用错误的编码来解析响应,并且responseText中的字符可能是错的。XHR2定义了overrideMimeType()来解决这个问题;大部分厂商都实现了这个方法。在send()之前把类型传递给overrideMimeType(),这将使XMLHttpRequest忽略“Content-Type”头而使用指定的类型。
编码请求主体
POST请求包括一个请求主体,它包含客户端给服务器的数据。请求主体是简单的文本字符串。但,我们通常使用 http请求发送的都是更复杂的数据。
表单编码的请求
用户提交表单时,表单中的数据(表单元素的名和值)编码到一个字符串中并随请求发送。默认情况下,html表单,通过post方法发送给服务器,而编码后的表单数据则用作请求主体。对表单数据使用的编码方案相对简单:对每个表单元素的名字和值执行普通的url编码(使用十六进制定义码替换特殊字符),使用等号把编码后的名字和值分开。并使用“&”符号分开名值对。如:
find=pizza&zipcode=01234&radius=1km
表单数据编码格式有一个正式的MIME类型:
application/x-www-form-urlencode
当POST方法提交这种顺序表单数据时,必须设置“Content-Type”请求头为这个值。注意这类编码并不需要html表单,在ajax应用中,你希望发送给服务器的很可能是一个js对象。如
{
find;”pizza”
,zipcode:01234
,radius:”1km”
}
表单编码在web上如此广泛,同时所有服务器端的编程语言都能良好的支持,所以非表单数据的表单编码通常也是容易实现的事情。
如:
//编码对象的属性
//如果它们是来自html表单名/值对,使用application/x-www-form-urlencode格式
function encodeFormData(data){
if(!data) return “”;
var pairs = [];
for(var name in data){
if(!data.hasOwnProperty(name))continue; //跳过继承属性
if(typeof data[name]===”function”)continue; //跳过方法
var value =data[name].toString(); //把值转换成字符串
name =encodeURIComponent(name.replace(“%20”,”+”));
value =encodeURIComponent(value.replace(“%20”,”+”));
pairs.push(name+”=”+value);
return pairs.join(“&”);
}
}
例如:使用表单编码数据发起一个http post请求
function postData(url,data,callback){
var request = new XMLHttpRequest();
request.open(“POST”,url);
request.onreadystatechange= functon(){
if(request.status==200 &&request.readyState===4 && callback){
callback(request);
}
}
request.setRequestHeader(“Content-Type”,”application/x-www-form-urlencode”);
request.send(encodeFormData(data)); //发送表单编码的数据
}
如:使用表单编码数据发送GET请求
function postData(url,data,callback){
var request = new XMLHttpRequest();
request.open(“GET”,url+”?”+ encodeFormData(data));
request.onreadystatechange= functon(){
if(request.status==200 &&request.readyState===4 && callback){
callback(request);
}
}
request.setRequestHeader(“Content-Type”,”application/x-www-form-urlencode”);
request.send(null); //发送表单编码的数据
}
JSON编码的请求
在POST请求主体中使用表单编码是常见惯例,但在任何情况下它都不是http协议的必须品。
function postData(url,data,callback){
var request = new XMLHttpRequest();
request.open(“POST”,url);
request.onreadystatechange= functon(){
if(request.status==200 &&request.readyState===4 && callback){
callback(request);
}
}
request.setRequestHeader(“Content-Type”,”application/json”);
request.send(JSON.stringify(data));
}
XML编码请求
<query>
<find zipcode=”01234” radius=”1km”>pizza</find>
</query>
function postData(url,what,where,radius,callback){
var request = new XMLHttpRequest();
request.open(“POST”,url);
request.onreadystatechange= functon(){
if(request.status==200 &&request.readyState===4 && callback){
callback(request);
}
}
var doc =document.implementation.createDocument(“”,”query”,null);
var query = doc.documentElement;
var find = doc.createElement(“find”);
query.appendChild(find);
find.setAttribute(“zipcode”,where);
find.setAttribute(“radius”, radius);
find.appendChild (doc.createTextNode(what));
//注意将自动设置Content-Type头
request.send(doc);
}
上传文件
html表单特性之一是当用户通过<input type=”file”>元素选择文件时,表单将在它产生的post请求主体中发送文件内容。html表单始终能上传文件,但到目前为止它还不能使用XMLHttpRequestAPI做这样的事情(目前通过formdata post)。XHR2 API允许通过send()方法传入File对象来实现上传文件。
没有File()对象构造函数,脚本仅能获取表示用户当前选择文件的File对象。在支持File对象的浏览器中,每个<input type="file">元素有一个files属性,它是File对象中的类数组对象。拖放API允许通过拖放事件的dataTransfer.files属性访问用户“拖放”到元素上的文件。可以将它当做一个用户选择文件完全不透明的表示形式,适用于通过send()来上传文件。例如下例是一个自然js函数,它对某些文件上传元素添加了change事件处理程序,这样它们能自动把任何选择过的文件内容通过POST方法自动发送到指定的URL。
//例,如:使用HTTP POST请求上传文件
//查找有data-uploadto属性的全部<inputtype="file">元素
//并注册onchange事件处理程序
//服务器的响应是忽略的
whenReady(function(){
var elts =document.getElementsByTagName("input");
for(var i=0;i<elts.length;i++){
var input = elts[i];
if(input.type!=="file")continue;
var url = input.getAttribute("data-uploadto");//获取上传的url
if(!url)continue;
input.addEventListener("change",function(){
var file = this.files[0]; //假设单个文件选择
if(!file) reutrn;
var xhr = newXMLHttpRequest(); //创建新请求
xhr.open("POST",url);
xhr.send(file); //把文件作为主体发送
},false);
}
});
我们在后面的内容中可以看到文件类型是更通用的二进制大对象(Blob)类型中的一个子类型。XHR2允许向send()方法传入任何Blob对象。如果没有显示设置Content-Type头,这个Blob对象的type属性用于设置待上传的Content-Type头
multipart/form-data请求
当html表单同时包含文件上传元素和其他元素时,浏览器不能使用普通的表单编码而必须使用称为“multipart/form-data”的特殊Content-Type来用POST方法提交表单。这种编码包括使用长“边界”字符串把请求主体分离成多个部分。对于文本数据,手动创建“multipart/form-data"请求主体是可能的。但很复杂。
XHR2 定义了新的FormData API,它容易实现多部分请求主体。首先,使用FormData()构造函数创建FormData对象,然后按需多次调用这个对象的append()方法把个体“部分”(可以是字符串,File或Blob对象)添加到请求中。最后,把FormData对象传递给send()方法。send()方法将对请求定义合适的边界字符串和设置“Content-Type"头。
例:使用POST方法发送multipart/form-data请求主体
function postFormData(url,data,callback){
if(typeof FormData ==="undefined") throw new Error("FormData is notimplemented");
var request = new XMLHttpRequest();
request.open("POST",url);
request.onreadystatechange = function(){
if(request.readyState==4 &&request.state==200 && callback){
callback(request);
}
};
for(var name in data){
if(!data.hasOwnProperty(name))continue; //跳过继承属性
var value = data[name];
if(typeofvalue==="function") continue; //跳过方法
//每个属性变成请求一个部分
//这里允许File对象
formdata.append(name,value);
}
request.send(formdata);
}
HTTP进度事件
之前的示例使用readystatechange事件探测HTTP请求的完成。XHR2规范草案定义了更多用的事件集,有些已经在firefox、Chrome和safari中得到支持。在这个新的事件模型中,XMLHttpRequest对象在请求的不同阶段触发不同的类型事件,所以它不再需要检查readyState属性。
当调用send()时,触发loadstart事件,当加载服务器响应时,XMLHttpRequest对象会发生progress事件,通常每隔50毫秒左右,所以可以使用这些事件给用户反馈请求的进度。如果请求快速完成,它可能从不触发progress事件。当事件完成,会触发load事件。
一个完成的请求不一定是成功的请求,例如load事件的处理程序应该检查XMLHttpRequest对象的status状态码来确定收到的是“200 OK”而不是“404 Not Found”的http响应。
HTTP请求无法完成有3种情况:请求超时,会触发timeout事件。如果请求中止,会触发abort事件,最后,像太多重定向这样的网络错误会阻止请求完成,但这种情况发生时会触发error事件。
对于任何具体请求,浏览器将只会触发load、abort、timeout和error事件中的一个,XHR2规范指出一旦事件中的一个发生后,浏览器应该触发loadend事件。浏览器尚未完成loadend事件。
可以通过XMLHttpRequest对象的addEventListener()方法为这些progress事件中的每个都注册处理程序。如果每种事件只有一个事件处理程序,通常更容易的方法是只设置对应的处理程序,比如onprogress和onload。
//测试浏览器是否支持
if("onprogress" in (newXMLHttpRequest())){
}
除了像type和timestamp这样常用的Event对象属性外,与这些progress事件相关联的事件对象还有3个有用的属性。
loaded属性是目前传输的字节数值。
total属性是自“Content-Length"头传输的数据的整体长度(单位是字节)如果不知道内容长度则为0
如果知道内容长度lengthComputable属性为true;否则为false。
显示,total和loaded属性对progress事件处理程序相当有用:
request.onpregress= function(e){
if(e.lengthComputable)progress.innerHTML =Math.round(100*e.loaded/e.total)+"% Complete;
}
上传进度事件
除了为监控HTTP响应的加载定义的这些有用的事件外,XHR2也给出了用于监控HTTP请求上传的事件。在实现这些特性的浏览器中,XMLHttpRequest对象资将有upload属性。
upload属性是一个对象,它定义了addEventListener()方法和整个progress事件集合。(但这个对象没有定义onreadystatechange属性,upload仅能触发新的事件类型)
下例中介绍了如何使用upload progress事件把上传进度反馈给用户。
//查找所有含有“fileDropTarget”类的元素
//并注册DnD事件处理程序使它们能响应文件的拖放
//当文件放下时,上传它们到data-uploadto属性指定的URL
whenReady(function(){
var elts = document.getElementsByClassName(“fileDropTarget”);
for(var i=0;i<elts.length;i++){
var target= elts[i];
var url =target.getAttribute(“data-uploadto”);
if(!url)continue;
createFileUploadDropTarget(target,url);
}
function createFileUploadDropTarget(target,url){
var uploading = false;
console.log(target,url);
target.οndragenter= function(e){
console.log(“dragenter”);
if(uploading)return ;//如果正在忙,忽略拖放
vartypes = e.dataTransfer.types;
if(types&& types.contains && types.contains(“Files”)) || (types.indexOf&& types.indexOf(“Files”)!==-1)){
target.classList.add(“wantdrop”);
return false;
}
}
}
target.ondragover = function(e){if(!uploading)return false};
target.οndragleave=function(e){if(!uploading)target.classList.remove(“wantdrop”);}
target.ondrop = function(e){
if(!uploading)return false;
var files = e.dataTransfer.files;
if(files && files.length){
uploading = true;
var message = “Uploading files:<ul>”;
for(var i=0;i<files.length;i++){message+=”<li>”+files[i].name+”</li>”;}
message+=”</ul>”;
target.innerHTML = message;
target.classList.remove(“wantdrop”);
target.classList.add(“uploading”);
var xhr = new XMLHttpRequest();
xhr.open(“POST”,url);
var body = new FormData();
for(var i=0;i<files.length;i++)body.append(i,files[i]);
xhr.upload.onprogress = function(e){
if(e.lengthComputable){
target.innerHTML =message+Math.round(e.loaded/e.total*100)+”% Complete”;
}
}
xhr.upload.onload = function(e){
uploading = false;
target.classList.remove(“uploading”);
target.innerHTML = “Drop files toupload”;
}
xhr.send(body);
return false;
}
target.classList.remove(“wantdrop”);
}
});
中止请求和超时
中止请求和超时
可以调用XMLHttpRequest对象的abort()方法来取消正在进行的http请求。abort()方法在所有的XMLHttpRequest版本和XHR2中可用,调用abort()方法在这个对象上触发abort事件。假设使用XMLHttpRequest为文本输入域请求自动完成推荐,如果用户在服务器的建议达到之前输入了新的字符,这时等待请求不再有趣,应该中止。
XHR2定义了timeout属性来指定请求自动中止后的毫秒数,也定义了timeout事件用于当超时发生时触发。也可以用setTimeout()和abort()方法实现自己的超时。
实现超时
//发起http get请求获取指定url的内容
//如果响应成功到达,传入responseText给回调函数
//如果响应在timeout毫秒内没有到达,中止这个请求
//浏览器可能在abort()后触发"readystatechange" 如果是部分请求结果到达,甚至可能是设置status属性
//所以需要设置一个标记,当部分且超时的响应到达时不会调用回调函数,如果使用load事件就没有这个风险
function timedGetText(url,timeout,callback){
var request = new XMLHttpRequest();
var timer = setTimeout(function(){
timeout = true;
request.abort();
},timeout);
request.open("GET",url);
request.onreadystatechange = function(){
if(request.readyState !=4) return;
if(timeout) return ;
if(request.status==200) callback(request.responseText);
}
request.send(null);
}