首页 > 代码库 > HTTP脚本化——XMLHttpRequest对象的学习笔记

HTTP脚本化——XMLHttpRequest对象的学习笔记

一、 HTTP 请求和响应

一个HTTP请求由4部分组成

  • HTTP请求方法(也叫动作Verb)
  • 正在请求的URL
  • 一个可选的请求头集合(可能包含身份验证信息等)
  • 一个可选的请求主体

服务器返回的HTTP响应由3部分组成

  •  一个数字和文字组成的状态码,用来显示请求的成功和失败
  •  一个响应头集合
  • 响应主体

说明:

  1. XMLHttpRequest不是协议级的HTTP API而是浏览器级的API,浏览器级的API需要考虑Cookie、重定向、缓存和代理,而协议级的API只需要考虑请求和响应
  2. XMLHttpRequest和本地文件: XMLHttpRequest针对的是HTTP协议,即URL不能是file://,所以测试的时候必须将文件上传到Web服务器或运行一个本地服务器
  3. 同源策略问题(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 functionfunction 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 = http://www.mamicode.com/data[name].toString();                     // Value as string    name = encodeURIComponent(name.replace(" ", "+"));   // Encode name    value = http://www.mamicode.com/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.2JSON-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 receivedfunction 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 =http://www.mamicode.com/=="undefined")   throw new Error("FormData is not implemented");var request = new XMLHttpRequest();               // New HTTP requestrequest.open("POST", url);                        // POST to the specified urlrequest.onreadystatechange = function() {           // A simple event handler.if (request.readyState === 4 && callback)         // When response is completecallback(request);                         // ...call the callback.};var formdata = http://www.mamicode.com/new FormData();for(var name in data) {if (!data.hasOwnProperty(name)) continue;      // Skip inherited propertiesvar value =http://www.mamicode.com/ data[name];if (typeof value =http://www.mamicode.com/=="function")  continue;       // Skip methods// Each property becomes one "part" of the request.// File objects are allowed hereformdata.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 objectrequest.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  IECORS的实现

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 requestscript.src = http://www.mamicode.com/url; // Set script urldocument.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 =http://www.mamicode.com/ url;    document.getElementsByTagName(“head”)[0].appendChild(script);}

说明: 最近换工作,面试的时候才发现自己的javascript的基本功还是差啊,这是以前急功近利,很多知识没有顾及到,得补( ⊙ o ⊙ )啊!

参考: JavaScript权威指南(第6版).JavaScript:The.Definitive.Guide.David.Flanagan