• 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 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.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 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  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 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

  • 相关阅读:
    12.16省选模拟t2 消防
    12.17省选模拟t3 围豆豆
    12.17省选模拟t1 生日礼物
    CF1322D Reality Show
    winform拖动无边框窗体
    关于ToolTip控件在XP系统中问题
    JDK源代码里面的一个for循环
    IIS5.1 无法运行asp.net网站但可访问静态页的解决方案
    winform窗体去掉标题头部的两种方式
    C# 语法之泛型
  • 原文地址:https://www.cnblogs.com/JoannaQ/p/3904130.html
Copyright © 2020-2023  润新知