超文本传输协议(HyperText Transfer Protocol,HTTP)规定Web浏览器如何从Web服务器获取文档和向Web服务器提交表单内容,以及Web服务器如何响应这些请求和提交。
- Ajax应用的主要特点是使用脚本操纵HTTP和Web服务器进行数据交换,不会导致页面重载。(Asynchronous JavaScript and XML)
- "Ajax"中的X表示XML,这些名字只是XML流行时的遗迹。
- Comet和Ajax相反。在Comet中,Web服务器发起通信并异步发送消息到客户端。
- 在Ajax中,客户端从服务端“拉”数据,而在Comet中,服务端向客户端“推”数据。
- 脚本通过设置<img>元素的src属性,且把信息作为图片URL的查询字符串部分,就把能经过编码信息传递给Web服务器。
- 为了把<iframe>作为Ajax传输协议使用,脚本首先要把发送给Web服务器的信息编码到URL中,然后设置<iframe>的src属性为该URL。服务器能创建一个包含响应内容的HTML文档,并把它返回给Web浏览器,并且在<iframe>中显示它。
- <script>元素的src属性能设置URL并发起HTTP GET请求。它们可以跨域通信而不受限于同源策略。使用基于<script>的Ajax传输协议时,服务器的响应采用JSON编码的数据格式,当执行脚本时,JavaScript解析器能自动将其“解码”,因此这种Ajax传输协议也叫做"JSONP"。
- XMLHttpRequest对象,它定义了用脚本操纵HTTP的API。除了常用的GET请求,这个API还包含实现POST请求的能力,它能获取任何类型的文本文档。
1. 使用XMLHttpRequest
XMLHttpRequest类的每个实例都表示一个独立的请求/响应对,并且这个对象的属性和方法允许指定请求细节和提取响应数据。
- 第一件事就是实例化XMLHttpRequest对象:
var request=new XMLHttpRequest();
一个HTTP请求由4部分组成:
- HTTP请求方法或“动作”(verb)
- 正在请求的URL
- 一个可选的请求头集合,其中可能包括身份验证信息
- 一个可选的请求主体 服务器返回的HTTP响应包含3部分:
- 一个数字和文字组成的状态码,用来显示请求的成功和失败
- 一个响应头集合
- 响应主体
- XMLHttpRequest不是协议级的HTTP API而是浏览器级的API。浏览器需要考虑cookie、重定向、缓存和代理,但代码只需要担心请求和响应。
- 如果从本地文件中加载网页,那么该页面中的脚本将无法通过相对URL使用XMLHttpRequest,因为这些URL将相对于
file://URL而不是http://URL。而同源策略通常会阻止使用绝对http://URL。因此当使用XMLHttpRequest时,为了测试它们通常必须把文件上传到Web服务器(或运行一个本地服务器)
1.1 指定请求
创建XMLHttpRequest对象之后,发起HTTP请求的下一步是调用XMLHttpRequest对象的open()方法去指定这个请求的两个必需部分:方法和URL。
request.open("GET",//开始一个HTTP GET请求
"data.csv");//URL的内容
open()的第一个参数指定HTTP方法或动作。
- GET"用于常规请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存时。
- "POST"方法常用于HTML表单。它在请求主体中包含额外数据(表单数据)且这些数据常存储到服务器上的数据库中(副作用)。
- 除了"GET"和"POST"之外,XMLHttpRequest规范也允许把"DELETE"、"HEAD"、"OPTIONS"和"PUT"作为open()的第1个参数。 open()的第2个参数是URL,它是请求的主题。这是相对于文档的URL
- 如果有请求头的话,请求进程的下个步骤是设置它。例如,POST请求需要"Content-Type"头指定请求主题的MIME类型:
request.setRequestHeader("Content-Type","text/plain");
- 如果对相同的请求头调用setRequestHeader()多次,新值不会取代之前指定的值,相反,HTTP请求将包含这个头的多个副本或这个头将指定多个值。
使用XMLHttpRequest发起HTTP请求的最后一步是指定可选的请求主体并使用
send()方法向服务器发送它。
request.send(null);
- GET请求绝对没有主体,所以应该传递null或省略这个参数。POST请求通常拥有主体,同时它应该匹配使用setRequestHeader()指定的"Content-Type"头。
- HTTP请求的各部分有指定顺序:请求方法和URL首先到达,然后是请求头,最后是请求主体。
- 调用XMLHttpRequest方法的顺序必须匹配HTTP请求的架构。例如,setRequestHeader()方法的调用必须在调用open()之前但在调用send()之后,否则它将抛出异常。
// 用POST方法发送纯文本给服务器
function postMessage(msg) {
var request = new XMLHttpRequest(); //新请求
request.open("POST", "/log.php"); //用POST向服务器端发送脚本
//用请求主体发送纯文本消息
request.setRequestHeader(
"Content-Type", //请求主体将是纯文本
"text/plain;charset=UTF-8"
);
request.send(msg); //把msg作为请求主体发送
//请求完成,我们将忽略任何响应和任何错误
}
1.2 取得响应
一个完整的HTTP响应由状态码、响应头集合和响应主体组成。这些都可以通过XMLHttpRequest对象的属性和方法使用:
- status和statusText属性以数字和文本的形式返回HTTP状态码。这些属性保存标准的HTTP值,像200和"OK"表示成功请求,404和"Not Found"表示URL不能匹配服务器上的任何资源。
- 使用getResponseHeader()和getAllResponseHeaders()能查询响应头
- 响应主体可以从responseText属性中得到文本形式的 XMLHttpRequest对象通常异步使用:发送请求后,send()方法立即返回,直到响应返回,它的响应方法和属性才有效。为了在响应准备就绪时得到通知,必须监听XMLHttpRequest对象上的readystatechange事件
- 为了监听readystatechange事件,请把事件处理函数设置为XMLHttpRequest对象的onreadystatechange属性。也能使用addEventListener()
- readyState是一个整数,它指定了HTTP请求的状态
1.2.1 同步响应
XMLHttpRequest也支持同步响应。如果把false作为第3个参数传递给open(),那么send()方法将阻塞直到请求完成。
1.2.2 响应解码
如果服务器想发送诸如对象或数组这样的结构化数据作为其响应,它应该传输JSON编码的字符串数据。此时该响应的"Content-Type"头为"application/json"。
- Web服务端通常使用二进制数据(例如,图片文件)响应HTTP请求。
- 在调用send()之前把类型传递给overrideMimeType(),这将使XMLHttpRequest忽略"Content-Type"头而使用指定的类型:
//重新指定响应的处理类型
request.overrideMimeType("text/plain;charset=utf-8")
1.3 编码请求主体
HTTP POST请求包括一个请求主体,它包含客户端传递给服务器的数据
1.3.1 表单编码请求
对表单数据使用的编码方案相对简单:对每个表单元素的名字和值执行普通的URL编码(使用十六进制转义码替换特殊字符),使用等号把编码后的名字和值分开,并使用“&”符号分开名/值对。
find=pizza&zipcode=02134&radius=1km
表单数据编码格式有一个正式的MIME类型,当使用POST方法提交这种顺序的表单数据时,必须设置"Content-Type"请求头为这个值:
application/x-www-form-urlencoded
GET请求从来没有主体,所以需要发送给服务器的表单编码数据“负载”要作为URL(后跟一个问号)的查询部分。
1.3.2 JSON编码请求
使用JSON.stringify()编码请求主体
request.setRequestHeader("Content-Type","application/json");
request.send(JSON.stringify(data));
1.3.3 multipart/form-data请求
当HTML表单同时包含文件上传元素和其他元素时,浏览器不能使用普通的表单编码而必须使用称为"multipart/form-data"的特殊Content-Type来用POST方法提交表单。
XHR2(XMLHttpRequest2)定义了新的FormData API,它容易实现多部分请求主体。首先,使用FormData()构造函数创建FormData对象,然后按需多次调用这个对象的append()方法把个体“部分”(可以是字符串、File或Blob对象)添加到请求中。最后,把FormData对象传递给send()方法。
var formdata=new FormData();
formdata.append(name,value);//作为一部分添加名/值对
request.send(formdata);
1.4 HTTP进度事件
- 当正在加载服务器的响应时,XMLHttpRequest对象会发生progress事件
- 当事件完成,会触发load事件。
- HTTP请求无法完成有3种情况,对应3种事件。如果请求超时,会触发timeout事件。如果请求中止,会触发abort事件。最后,像太多重定向这样的网络错误会阻止请求完成,但这些情况发生时会触发error事件。
1.4.1 上传进度事件
XMLHttpRequest对象将有upload属性。upload属性值是一个对象,它定义了addEventListener()方法和整个progress事件集合,比如onprogress和onload。
对于XMLHttpRequest对象x,设置x.onprogress以监控响应的下载进度,并且设置x.upload.onprogress以监控请求的上传进度。
1.5 中止请求和超时
- 可以通过调用XMLHttpRequest对象的abort()方法来取消正在进行的HTTP请求。
- HR2定义了timeout属性来指定请求自动中止后的毫秒数,也定义了timeout事件用于当超时发生时触发
1.6 跨域HTTP请求
XHR2通过在HTTP响应中选择发送合适的CORS(Cross-Origin Resource Sharing,跨域资源共享)允许跨域访问网站。
跨域请求通常也不会包含其他任何的用户证书:cookie和HTTP身份验证令牌(token)通常不会作为请求的内容部分发送且任何作为跨域响应来接收的cookie都会丢弃。
作为Web程序员,使用这个功能并不需要做什么额外的工作:如果浏览器支持XMLHttpRequest的CORS且实现跨域请求的网站决定使用CORS允许跨域请求,那么同源策略将不放宽而跨域请求就会正常工作。
测试withCredentials的存在性是测试浏览器是否支持CORS的一种方法
var supportsCORS=(new XMLHttpRequest()).withCredentials!==undefined;
2. 借助<script>发送HTTP请求:JSONP
只须设置<script>元素的src属性(假如它还没插入到document中,需要插入进去),然后浏览器就会发送一个HTTP请求以下载src属性所指向的URL。
- 使用<script>元素进行Ajax传输的一个主要原因是,它不受同源策略的影响
- 需要注意的是,这种方式普遍用于可信的第三方脚本
- 这种使用<script>元素作为Ajax传输的技术称为JSONP,P代表“填充”或“前缀”
3. 基于服务器端推送事件的Comet技术
在服务器端推送事件的标准草案中定义了一个EventSource对象,简化了Comet应用程序的编写可以传递一个URL给EventSource()构造函数,然后在返回的实例上监听消息事件。
var ticker = new EventSource("stockprices.php");
ticker.onmessage = function(e) {
var type = e.type;
var data = e.data; //现在处理事件类型和事件的字符串数据
};
服务器端推送事件的协议很简单。客户端(创建一个EventSource对象时会)建立一个到服务器的连接,服务器保持这个连接处于打开状态。
一个使用EventSource的简易聊天客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style></style>
</head>
<body>
<!--聊天的UI只是一个单行文本域-->
<!--新的聊天消息会插入input域之前-->
<input id="input" style="width:100%" />
<script>
window.onload = function() {
//注意一些UI细节
var nick = prompt("Enter your nickname"); //获取用户昵称
var input = document.getElementById("input"); //找出input表单元素
input.focus(); //设置键盘焦点
//通过EventSource注册新消息的通知
var chat = new EventSource("/chat");
chat.onmessage = function(event) {
//当捕获一条消息时
var msg = event.data; //从事件对象中取得文本数据
var node = document.createTextNode(msg); //把它放入一个文本节点
var div = document.createElement("div"); //创建一个<div>
div.appendChild(node); //将文本节点插入div中
document.body.insertBefore(div, input); //将div插入input之前
input.scrollIntoView(); //保证input元素可见
};
//使用XMLHttpRequest把用户的消息发送给服务器
input.onchange = function() {
//用户完成输入
var msg = nick + ":" + input.value; //组合用户名和用户输入的信息
var xhr = new XMLHttpRequest(); //创建新的XHR
xhr.open("POST", "/chat"); //发送到/chat
xhr.setRequestHeader(
"Content-Type", //指明为普通的UTF-8文本
"text/plain;charset=UTF-8"
);
xhr.send(msg); //发送消息
input.value = ""; //准备下次输入
};
};
</script>
</body>
</html>