一、 HTTP 请求和响应
一个HTTP请求由4部分组成
- HTTP请求方法(也叫动作Verb)
- 正在请求的URL
- 一个可选的请求头集合(可能包含身份验证信息等)
- 一个可选的请求主体
服务器返回的HTTP响应由3部分组成
- 一个数字和文字组成的状态码,用来显示请求的成功和失败
- 一个响应头集合
- 响应主体
说明:
- XMLHttpRequest不是协议级的HTTP API而是浏览器级的API,浏览器级的API需要考虑Cookie、重定向、缓存和代理,而协议级的API只需要考虑请求和响应
- XMLHttpRequest和本地文件: XMLHttpRequest针对的是HTTP协议,即URL不能是file://,所以测试的时候必须将文件上传到Web服务器或运行一个本地服务器
- 同源策略问题(same-origin policy),所以测试的时候必须将文件上传到Web服务器或运行一个本地服务器
二、 使用XMLHttpRequest对象
1、 实例化XMLHttpRequest对象
var request = new XMLHttpRequest(); // Emulate the XMLHttpRequest() constructor in IE5 and IE6 if (window.XMLHttpRequest === undefined) { window.XMLHttpRequest = function() { try { // Use the latest version of the ActiveX object if available return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }catch (e1) { try { // Otherwise fall back on an older version return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch(e2) { // Otherwise, throw an error throw new Error("XMLHttpRequest is not supported"); } } }; }
说明: 在IE5和IE6中的XMLHttpRequest只是一个ActiveX对象,IE7之前的版本不支持非标准的XMLHttpRequest() 构造函数
2、 简单的指定请求和发送请求步骤
如用POST方法发送纯文本给服务器请求完成
function postMessage(msg) { var request = new XMLHttpRequest(); // ①New request request.open("POST", "/log.php"); // ②POST to a server-side script // Send the message, in plain-text, as the request body request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); //③Request body will be plain text request.send(msg); // ④Send msg as the request body // The request is done. We ignore any response or any error. }
说明:
2.1)指定请求——XMLHttpRequest的open()方法
Request.open(“方法”,”URL”);
2.2)设置请求头——XMLHttpRequest的setRequestHeader()方法
request.setRequestHeader("Content-Type", "text/plain");
说明:
- 如果对相同的头调用setRequestHeader多次,新值不会取代旧值,反之,HTTP请求将包含这个头的多个副本或这个头将指定多个值
- 不能指定的头信息的值包括(XMLHttpRequest将自动添加这些头而防止伪造它们), 能指定“Authorization”,但通常不需要这么做,一般会通过open()的可选的第三参数来设置用户名和密码
2.3)发出请求——XMLHttpRequest的send()方法
request.send(参数);
- GET请求没有主体,应传递null或省略这个参数
- POST请求通常拥有主体,并应匹配使用setRequestHeader()指定的“Content-Type”头
2.4)请求的顺序问题——所以上面的三个方法的顺序不能错,否则将抛出异常
- 请求方法和URL首先到达
- 请求头
- 请求主体
3、取得响应
上面的例子中忽略了任何响应和任何错误,只管发送,下面我们开始讨论如何取得响应并根据响应进行处理。
如下例——如果①请求完成,②请求成功且③响应主体是文本,才会把响应主体发送给指定的回调函数
// Issue an HTTP GET request for the contents of the specified URL. // When the response arrives successfully, verify that it is plain text // and if so, pass it to the specified callback function function getText(url, callback) { var request = new XMLHttpRequest(); // ①Create new request request.open("GET", url); // ② Specify URL to fetch request.onreadystatechange = function() { // ③Define event listener // If the request is compete and was successful if (request.readyState === 4 && request.status === 200) { var type = request.getResponseHeader("Content-Type"); if (type.match(/^text/)) // Make sure response is text callback(request.responseText); // Pass it to callback } }; request.send(null); // ④Send the request now }
3.1)readystatechange事件
理论上,每次readyState属性改变均会触发readystatechange事件。实际中
3.2)readyState值
理论上,每次readyState属性改变均会触发readystatechange事件。但实际中
- readyStart为0和1时候可能没触发该事件
- 调用send()时候,即使readyState仍处于OPENED状态,也触发该事件
- 某些浏览器在LOADING状态时也触发该事件
- 当readyState变为4或服务器的响应完成时,所有浏览器均触发该事件
在老的浏览器和IE8中没有定义readyState的常量部分,这个时候应使用编码值进行处理。
3.3)getResponseHeader(),getAllResponseHeader()——查询响应头
说明:XMLHttpRequest会自动处理cookie,所以getResponseHeader()取得的”Set-Cookie”和”Set-Cookie2”则返回null
3.4)同步响应Synchronous
XMLHttpRequest对象默认异步响应,如果先进行同步响应,设置open()的第三参数为false
// Issue a synchronous HTTP GET request for the contents of the specified URL. // Return the response text or throw an error if the request was not successful // or if the response was not text. function getTextSync(url) { var request = new XMLHttpRequest(); // Create new request request.open("GET", url, false); // Pass false for synchronous request.send(null); // Send the request now // Throw an error if the request was not 200 OK if (request.status !== 200) throw new Error(request.statusText); // Throw an error if the type was wrong var type = request.getResponseHeader("Content-Type"); if (!type.match(/^text/)) throw new Error("Expected textual response; got: " + type); return request.responseText; }
3.5)响应解码——响应头主体类型
XMLHttpRequest对象的ResponseText, ResponseXML等属性可以得到响应主体的MIME类型,我们可以根据不同的类型分别将不同的对象发送给回调函数进行处理
// Issue an HTTP GET request for the contents of the specified URL. // When the response arrives, pass it to the callback function as a // parsed XML Document object, a JSON-parsed object, or a string. function get(url, callback) { var request = new XMLHttpRequest(); // Create new request request.open("GET", url); // Specify URL to fetch request.onreadystatechange = function() { // Define event listener // If the request is compete and was successful if (request.readyState === 4 && request.status === 200) { // Get the type of the response var type = request.getResponseHeader("Content-Type"); // Check type so we don't get HTML documents in the future if (type.indexOf("xml") !== -1 && request.responseXML){ callback(request.responseXML); // XML response }else if (type === "application/json"){ // JSON response, 先使用JSON.parse将对象转换为JSON对象 callback(JSON.parse(request.responseText)); }else{ callback(request.responseText); // String response } }; request.send(null); // Send the request now }
说明:application/javascript或text/javascript响应类型情况下不需要使用XMLHttpRequest对象,以为<script>对象本身能操作HTTP来实现加载并执行脚本。
4、请求主体编码(如何发送不同格式如form,JSON,XML,file,mulipart请求主体-数据到服务器)
4.1) Form-encoded request
HTML表单通过POST方法发送给服务器时候,对每个表单元素的名字和值执行普通的URL编码(使用16机制替换特殊字符),使用等号把编码的名字和值分开,并使用“&”分开名值对,如:
find=pizza&zipcode=02123&radius=1km
编码函数——将数据编码为名值对的形式
/*** Encode the properties of an object as if they were name/value pairs from */ function encodeFormData(data) { if (!data) return ""; // Always return a string var pairs = []; // To hold name=value pairs for(var name in data) { // For each name if (!data.hasOwnProperty(name)) continue; // Skip inherited if (typeof data[name] === "function") continue; // Skip methods var value = data[name].toString(); // Value as string name = encodeURIComponent(name.replace(" ", "+")); // Encode name value = encodeURIComponent(value.replace(" ", "+")); // Encode value pairs.push(name + "=" + value); // Remember name=value pair } return pairs.join('&'); // Return joined pairs separated with & }
HTTP POST request with form-encoded data
function postData(url, data, callback) { var request = new XMLHttpRequest(); request.open("POST", url); // POST to the specified url request.onreadystatechange = function() { // Simple event handler if (request.readyState === 4 && callback) // When response is complete callback(request); // call the callback. }; // Set Content-Type request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.send(encodeFormData(data)); // Send form-encoded data }
GET request with form-encoded data ——适用于简单的只读查询情况
function getData(url, data, callback) { var request = new XMLHttpRequest(); request.open("GET", url + "?" + encodeFormData(data)); // with encoded data added request.onreadystatechange = function() { // Simple event handler if (request.readyState === 4 && callback) callback(request); }; request.send(null); // Send the request }
4.2)JSON-encoded request
function postJSON(url, data, callback) { var request = new XMLHttpRequest(); request.open("POST", url); // POST to the specified url request.onreadystatechange = function() { // Simple event handler if (request.readyState === 4 && callback) // When response is complete callback(request); // call the callback. }; request.setRequestHeader("Content-Type", "application/json"); request.send(JSON.stringify(data)); }
4.3) XML-encoded request
// Encode what, where, and radius in an XML document and post them to the // specified url, invoking callback when the response is received function postQuery(url, what, where, radius, callback) { var request = new XMLHttpRequest(); request.open("POST", url); // POST to the specified url request.onreadystatechange = function() { // Simple event handler if (request.readyState === 4 && callback) callback(request); }; // Create an XML document with root element <query> var doc = document.implementation.createDocument("", "query", null); var query = doc.documentElement; // The <query> element var find = doc.createElement("find"); // Create a <find> element query.appendChild(find); // And add it to the <query> find.setAttribute("zipcode", where); // Set attributes on <find> find.setAttribute("radius", radius); find.appendChild(doc.createTextNode(what)); // And set content of <find> // Now send the XML-encoded data to the server. // Note that the Content-Type will be automatically set. request.send(doc); }
4.4)上传文件
// 查找有data-uploadto 属性的全部<input type="file"> 元素 // 并注册onchange handler 事件处理程序 //这样任何选择的文件都会自动通过POST方法发送到指定的 "uploadto" URL // 服务器的响应是忽略的 whenReady(function() { // Run when the document is ready var elts = document.getElementsByTagName("input"); // All input elements for(var i = 0; i < elts.length; i++) { // Loop through them var input = elts[i]; if (input.type !== "file") continue; // Skip all but file upload elts var url = input.getAttribute("data-uploadto"); // Get upload URL if (!url) continue; // Skip any without a url input.addEventListener("change", function() { // When user selects file var file = this.files[0]; // Assume a single file selection if (!file) return; // If no file, do nothing var xhr = new XMLHttpRequest(); // Create a new request xhr.open("POST", url); // POST to the URL xhr.send(file); // Send the file as body }, false); } });
4.5) multipart/form-data request——当表单中同时包含文件上传和其他元素
function postFormData(url, data, callback) { if (typeof FormData === "undefined") throw new Error("FormData is not implemented"); var request = new XMLHttpRequest(); // New HTTP request request.open("POST", url); // POST to the specified url request.onreadystatechange = function() { // A simple event handler. if (request.readyState === 4 && callback) // When response is complete callback(request); // ...call the callback. }; var formdata = new FormData(); for(var name in data) { if (!data.hasOwnProperty(name)) continue; // Skip inherited properties var value = data[name]; if (typeof value === "function") continue; // Skip methods // Each property becomes one "part" of the request. // File objects are allowed here formdata.append(name, value); // Add name/value as one part } // Send the name/value pairs in a multipart/form-data request body. Each // pair is one part of the request. Note that send automatically sets // the Content-Type header when you pass it a FormData object request.send(formdata); }
5、HTTP进度事件(略)
6、 中止请求和超时 (略)
三、 CORS(Cross-Origin Resource Sharing)——跨域资源共享
由于同源策略,默认情况下浏览器不容许XMLHttpRequest进行跨域请求
CORS是W3C的一个工作草案,其基本思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败,如
- Access-Control-Allow-Origin
- Access-Control-Allow-Credentials (optional)
默认情况下CORS请求不能包含cookies,如果设置withCredentials为TRUE,就可以发凭据的请求了(详见下)
3.1 IE对CORS的实现
IE8引入了XDR(XDomainRequest)来实现安全可靠的跨域通信,其安全机制部分实现了CORS规范
3.2 其他浏览器对CORS的实现
Firefox 3.5+、Safari4+、Chrome和Android平台中的Webkit都通过XMLHttpRequest对象来实现对CORS的原生支持。
跨域XHR对象的安全限制有
- 不能使用setRequestHeader()设置自定义头部
- 不能发送和接受cookie
- 调用getAllResponseHeader()方法总会返回空字符串
3.3 跨浏览器的CORS的实现
- Preflightted请求——CORS通过一种叫做Preflightted Requests的透明服务器验证机制支持开发人员使用自定义的头部、Get或Post自我的方法,以及不同类型的主体内容。
- 带凭据的请求——默认情况下,跨域请求不提供凭据(cookie、HTTP认证及客户端SSL证明等),通过设置withCredentials属性为true,可以指定某个请求应该发送凭证,如果服务器端接受了带凭据的请求,会用下面的HTTP头部来响应
Access-Control-Allow-Credentials: true
- 跨浏览器的CORS
检查存在withCredential属性,再结合检测XDomainRequest对象是否存在,就可以兼顾所有浏览器了
function createCORSRequest(method, url){ var xhr = new XMLHttpRequest(); if (“withCredentials” in xhr){ xhr.open(method, url, true); } else if (typeof XDomainRequest != “undefined”){ xhr = new XDomainRequest(); xhr.open(method, url); } else { xhr = null; } return xhr; } var request = createCORSRequest(“get”, “http://www.somewhere-else.com/page/”); if (request){ request.onload = function(){ //do something with request.responseText }; request.send(); }
XMLHttpRequest对象和XDomainRequest对象共同的属性/方法如下:
- abort() — Use to stop a request that’s already in progress.
- onerror — Use instead of onreadystatechange to detect errors.
- onload — Use instead of onreadystatechange to detect successes.
- responseText — Use to get contents of response.
- send() — Use to send the request.
四、 其他跨域技术
4.1 JSONP——借助<script>请求发送HTTP请求
使用<script>元素作为Ajax传送的技术称为JSONP,适用于HTTP请求所得到的响应数据是JSON编码的情况下。
JSONP看起来与JSON差不多,只不过是被包含在函数调用中的JSON,如
callback({ “name”: “Nicholas” });
回调函数({数据});
其优点有:
- 不受同源策略的影响
- 包含JSON编码的响应数据会自动解码(即,执行),可直接用相应的方法读取处理数据
// Make a JSONP request to the specified URL and pass the parsed response data to the specified callback. // Add a query parameter named "jsonp" to // the URL to specify the name of the callback function for the request. function getJSONP(url, callback) { // Create a unique callback name just for this request var cbnum = "cb" + getJSONP.counter++; // Increment counter each time var cbname = "getJSONP." + cbnum; // As a property of this function // Add the callback name to the url query string using form-encoding // We use the parameter name "jsonp". Some JSONP-enabled services // may require a different parameter name, such as "callback". if (url.indexOf("?") === -1) // URL doesn't already have a query section url += "?jsonp=" + cbname; // add parameter as the query section else // Otherwise, url += "&jsonp=" + cbname; // add it as a new parameter. // Create the script element that will send this request var script = document.createElement("script"); // Define the callback function that will be invoked by the script getJSONP[cbnum] = function(response) { try { callback(response); // Handle the response data } finally { // Even if callback or response threw an error delete getJSONP[cbnum]; // Delete this function script.parentNode.removeChild(script); // Remove script } }; // Now trigger the HTTP request script.src = url; // Set script url document.body.appendChild(script); // Add it to the document } getJSONP.counter = 0; // A counter we use to create unique callback names
简单一点的代码
Function loadJSON(url){ var script = document.createElement(“script”); script.type = “text/javascript”; script.src = url; document.getElementsByTagName(“head”)[0].appendChild(script); }
说明: 最近换工作,面试的时候才发现自己的javascript的基本功还是差啊,这是以前急功近利,很多知识没有顾及到,得补( ⊙ o ⊙ )啊!
参考: JavaScript权威指南(第6版).JavaScript:The.Definitive.Guide.David.Flanagan