灏天阁

脚本话HTTP

· Yin灏

XMLHttpRequest 基础

XMLHttpRequest 是客户端的一个 API,它为浏览器与服务端的通信提供了一个便捷的通道。

创建 XMLHttpRequest 对象

var xhr = new  XMLHttpRequest();

下面使用工厂模把定义 XMLHttpRequest 对象进行封装:

function createXHR () {
  var XHR = [ // 兼容不同浏览器和版本的创建函数数组
    function () { return new XMLHttpRequest() },
    function () { return new ActiveXObject("Msxml2.XMLHTTP") },
    function () { return new ActiveXObject("Msxml3.XMLHTTP") },
    function () { return new ActiveXObject("Microsoft.XMLHTTP") },
  ];
  var xhr = null;
  // 尝试调用函数,如果成功就返回 XMLHttpRequest 对象,否则就继续尝试
  for(var i = 0; i < XHR.length; i++) {
    try {
      xhr = XHR[i]();
    } catch(e) {
      continue; // 如果发生异常,则继续下一个函数
    }
    break; // 如果成功,则终止循环
  }
  return xhr;
}

建立链接

使用 XMLHttpRequest 对象的 open() 方法可以建立一个 HTTP 请求。

xhr.open(method, url, async, username, password);

建立连接后,可以使用 send() 方法发送请求。

xhr.send(body);
// body 是要发送的数据,如果不传递信息,可以设置为 null 或 省略

发送请求后,可以使用 XMLHttpRequest 对象的 responseBody、responseStream、responseText、responseXML 属性等待接收响应数据。

// 演示实现异步通信的方法
var xhr = createXHR();
xhr.open("GET", "server.txt", false); // 建立连接,要求同步响应
console.log(xhr.responseText); // 接收数据

发送 GET 请求

window.onload = function() {
    var b = document.getElementsByTagName("input")[0];
    b.onclick = function() {
        var url = "server.php?callback=functionName" // 设置查询字符串
        var xhr = createXHR();
        xhr.open("GET", url, false); // 同步响应
        xhr.send(null);
        console.log(xhr.responseText); // 接收数据
    }
}

发送 POST 请求

window.onload = function() {
    var b = document.getElementsByTagName("input")[0];
    b.onclick = function() {
        var url = "server.php" // 设置查询字符串
        var xhr = createXHR();
        xhr.open("POST", url, false); // 同步响应
        // 表示设置发送的是表单值,一般 POST 发送请求都必须设置这个值。否则服务器无法识别传递过来的数据
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send("callback=functionName");
        console.log(xhr.responseText); // 接收数据
    }
}

串行格式化数据

GET 和 POST 方法都是以键值对的字符串格式发送数据的。

  • 对象信息
{ user: "css8", pass: "123456", email: "css@mysite.cn" }

将 JSON 数据转换为串行格式化显示:

'user="css8"&pass="12345"&email="css@mysite.cn"'
  • 数组信息
[{name: "user", value: "css8"},{name: "pass", value: "123456"},{name: "email", value: "css@mysite.cn"}]

将上面的数据转换为串行格式化显示:

'user="css8"&pass="12345"&email="css@mysite.cn"'
// 定义一个工具函数,该函数能够把数据转换为串行格式化字符串并返回
function JSONtoString(data) {
    var a = [];
    if(data.constructor == Array) {
        for(var i = 0; i < data.length; i++) {
            a.push(data[i].name + "=" + encodeURIComponent(data[i].value));
        }
    } else {
        for(var i in data) {
            a.push(i + "=" + encodeURIComponent(data[i]))
        }
    }
    return a.join("&");
}

异步响应状态

使用 readyState 属性可以实时跟踪异步响应状态,当该属性发生变化的时候,会触发 readystatechange 事件,调用绑定的回调函数,readState 属性值如下表:

返回值 说明
0 未初始化,表示对象已经建立,但是尚未初始化,尚未调用 open() 方法
1 初始化,表示对象已经建立,尚未调用 send() 方法
2 发送数据,表示 send() 方法已经调用,但是当前的状态及 HTTP 头未知
3 数据传送中,已经接收部分数据,因为响应及 HTTP 头不全,这时通过 responseBody 和 responseText 获取部分数据会出现错误。
4 完毕。数据接收完毕,此时可以通过 responseBody 和 responseText 获取完整的响应数据。
window.onload = function() {
  var b = document.getElementsByTagName('input')[0];
  b.onclick = function() {
    var url = "server.php";
    var xhr = createXHR();
    xhr.open("POST", url, true); // 异步响应
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.statue == 200 || xhr.status == 0) {
          console.log(xhr.responseeText)
        }
      }
    }
    xhr.send("callback=functionName")
  }
}

中止请求

使用 abort() 方法可以中止正在进行的请求。

xhr.onreadystatechange = function() {};
xhr.abort();

在调用 abort() 方法前,应先清除 onreadystatechange 事件处理函数,因为 IE 和 Mozilla 在请求中止后也会激活这个事件处理函数,如果给 onreadystatechange 属性设置为 null,则 IE 会发生异常,所以可以为它设置一个空函数。

获取 XML 数据

XMLHttpRequest 对象通过 responseText、responseBody、responseStream、responseXML 属性获取响应信息,他们都是只读属性。

响应信息 说明
responseBody 将响应信息正文以 Unsigned Byte数组形式返回
responseStream 以 ADO Stream 对象的形式返回响应信息
responseText 将响应信息作为字符串返回
responseXML 将响应信息格式化为 xml 文档格式返回

在实际开发中,一般将格式设置为 xml、html、json 或其他纯文本格式。

  • 如果向页面中添加大块数据,选择 html 格式会比较方便。
  • 如果需要协作开发,且项目庞杂,选择 xml 格式会更通用。
  • 如果要检索复杂的数据,且结构复杂,那么选择 json 格式会更加轻便。
// 示例

/*
  // 在服务端创建一个简单的 xml 文档
  <?xml version="1.0" encoding="utf-8"?>
  <the>xml 数据</the>
*/

// <input name="submit" type="button" id="submit" value="向服务端发出请求" />
window.onload = function() {
  var b = document.getElementsByTagName('input')[0];
  b.onclick = function() {
    var xhr = createXHR();
    xhr.open("GET", "server.xml", true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.status == 200 || xhr.status == 0) {
          var info = xhr.responseXML;
          console.log(info.getElementsByTagName('the')[0].firstChild.data);
        }
      }
    }
    xhr.send()
  }
}

获取 html 字符串

// <input name="submit" type="button" id="submit" value="向服务端发出请求" />
window.onload = function() {
  var b = document.getElementsByTagName('input')[0];
  b.onclick = function() {
    var xhr = createXHR();
    xhr.open("GET", "server.xml", true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.status == 200 || xhr.status == 0) {
          var o = document.getElementById('grid');
          o.innerHTML = xhr.responseText;
        }
      }
    }
    xhr.send();
  }
}

获取 JavaScript 脚本

设计响应信息为 js 代码,与 json 数据不同,它是可执行命令或脚本。

在服务器请求文件中包含下面一个函数:

function () {
    var d = new Date();
    return d.toString();
}

客户端请求:

// <input name="submit" type="button" id="submit" value="向服务端发出请求" />
window.onload = function() {
  var b = document.getElementsByTagName('input')[0];
  b.onclick = function() {
    var xhr = createXHR();
    xhr.open("GET", "server.js", true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.status == 200 || xhr.status == 0) {
          var info = xhr.responseText;
          var o = eval("("+info+")"+"()");
          console.log(o);
        }
      }
    }
    xhr.send();
  }
}

获取 JSON 数据

使用 responseText 可以获取 json 格式的字符串,然后使用 eval() 方法将其解析为本地 js 脚本,再从该数据对象中读取信息。

服务端请求文件中包含下面 json 数据:

{ user: "css8", pass: "123456", email: "css8@mysite.cn" }

客户端获取 json 数据:

// <input name="submit" type="button" id="submit" value="向服务端发出请求" />
window.onload = function() {
  var b = document.getElementsByTagName('input')[0];
  b.onclick = function() {
    var xhr = createXHR();
    xhr.open("GET", "server.js", true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.status == 200 || xhr.status == 0) {
          var info = xhr.responseText;
          var o = eval("("+ info +")");
          console.log(info);
          console.log(o.user);
        }
      }
    }
    xhr.send();
  }
}
/*
  eval() 在解析 json 字符串时存在安全隐患,如果 json 字符串中包含恶意代码,在调用回调函数时可能会被执行,解决办法:先对 json 字符串进行过滤,屏蔽掉敏感或是恶意代码。也可以方位 www.json.org/json2.js 下载 js 版本解析程序。如果确认返回是安全的,可以不过滤。
*/

获取纯文本

服务端响应信息为字符串 “true”,客户端设计:

window.onload = function() {
  var b = document.getElementsByTagName('input')[0];
  b.onclick = function() {
    var xhr = createXHR();
    xhr.open("GET", "server.js", true);
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.status == 200 || xhr.status == 0) {
          var info = xhr.responseText;
          if(info == 'true') console.log("文本信息传输完整");
          else console.log("文本信息可能存在丢失");
        }
      }
    }
    xhr.send();
  }
}

获取和设置头部信息

HTTP 请求和响应都包含一组头部信息,获取和设置头部消息可以使用下面两个方法:

  • getAllResponseHeaders(): 获取响应的 HTTP 头部消息。
  • getResponseHeaders(“Header-name”): 获取指定的 HTTP 头部信息。
// 下面将获取 HTTP 响应的所有头部消息
var xhr = createXHR();
var url = "server.txt";
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
    if(xhr.readyState == 4 && xhr.status == 200) {
        console.log(xhr.getAllResponseHeaders()); // 获取头部消息
    }
}
/*
  获取某个指定的头部信息:
    - xhr.getResponseHeaders("Content-Type")
  还可以在发送请求中,设置各种头部信息:
    - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
*/

认识 XMLHttpRequest 2.0

XMLHttpRequest 1.0 API 存在一下缺陷:

  • 只支持文本数据的传送,无法用来读取和上传二进制文件。
  • 传送和接收数据时,没有进度信息,只能提示有没有完成。
  • 受同域限制,只能向同一个域的服务器请求数据。

2014 年 11 月 w3c 正式发布 XMLHttpRequest Level 2 标准规范,新增了很多实用功能:

  • 可以设置 HTTP 请求的时限。
  • 可以使用 FormData 对象管理表单数据。
  • 可以上传文件。
  • 可以请求不同域名下的数据(跨域请求)。
  • 可以获取服务端的二进制数据。
  • 可以获的数据传输的进度信息。

请求时限

xhr.timeout = 3000;
xhr.ontimeout = function(event) {
    console.log('请求超时!')
}

FormData 数据对象

var formData = new FormData(); // 新建对象
// 为对象添加表单项
formData.append('user', '张三');
formData.append('pass', 123456);
// 直接传送 formData 对象
xhr.send(fromData);

// FormData 对象也可以直接获取网页表单的值
var form = document.getElementById('myform');
var formData = new formData(form);
formData.append('grade', '2'); // 添加一个表单项
xhr.open("POST", from.action);
xhr.send(formData);

上传文件

设计一个选择文件的表单元素(input[type=file]),并将它装入 FormData 对象。

var formData = new FormData();
for(var i = 0; i < files.length; i++) {
    formData.append('files[]', files[i]);
}

// 发送 FormData 对象给服务器

响应不同类型数据

新版的 XMLHttpRequest 对象新增 responseType 和 response 属性。

  • responseType: 用于指定服务端返回数据的数据类型,可用值为:text、arraybuffer、blob、json、document。如果该属性设置为空字符串或不使用该属性,则默认为 text。

  • response: 如果向服务器提交请求成功,则返回响应数据。

  1. 如果 responseType 为 text,则 response 返回值为一串字符串。
  2. 如果 responseType 为 arraybuffer,则 response 返回值为一个 ArrayBuffer 对象。
  3. 如果 responseType 为 blob,则 response 返回值为一个 Blob 对象。
  4. 如果 responseType 为 json,则 response 返回值为一 JSON 对象。
  5. 如果 responseType 为 document,则 response 返回值为一串 Document 对象。

接收二进制数据

// 可以把 responseType 设为 blob,表示服务器传回的是二进制对象。
var xhr = new XMLHttpRequest();
xhr.open("GET", '/path/to/image.png');
xhr.responseType = 'blob';
// 接收数据的时候,用浏览器自带的 Blob 对象即可。
var blob = new Blob([xhr.response], {type: 'image/png'});
// 可以将 responseType 设置 arraybuffer,把二进制数据装在一个数组里。
var xhr = new XMLHttpRequest();
xhr.open("GET", '/path/to/image.png');
xhr.responseType = 'arraybuffer';

// 接收数据的时候需要遍历数组
var arrayBuffer = xhr.response;
if(arrayBuffer) {
    var byteArray = new Uint8Array(arrayBuffer);
    for(var i = 0; i < byteArray.byteLength; i++) {
        // 执行代码
    }
}

监测数据传输进度

新版新增了一个 progress 事件,同来返回进度信息。它分成上传和下载两种情况。

下载的 progress 事件属于 XMLHttpRequest 对象,上传的 progress 事件属于 XMLHttpRequest.upload 对象。

// 先定义 progress 事件的回调函数
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
// 在回调函数里面,使用和这个事件的一些属性
function updateProgress(event) {
    if(event.lengthComputable) {
        var percentComplete = event.loaded / event.total;
    }
}

上面代码这种,event.total 是需要传输的总字节,event.loaded 是已经传输的字节,如果 event.lengthComputable 不为真,则 event.total 等于 0。

与 progress 事件相关的,还有其他 5 个事件,可以分别指定回调函数。

  • load: 传输成功完成。
  • abort: 传输被用户取消。
  • error: 传输中出现错误。
  • loadstart: 传输开始。
  • loadEnd: 传输结束,但是不知道成功还是失败。

案例实战

接收字符串

// 接收服务端返回的字符串
function sendText() {
    var txt=document.getElementById("text1").value;
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'test.php', true);
    xhr.responseType = 'text';
    xhr.onload = function(e) {
        if (this.status == 200) {
            document.getElementById("result").innerHTML=this.response;
        }
    }; 
    xhr.send(txt);
}

发送表单数据

function sendForm() {
    var form=document.getElementById("form1");
    var formData = new FormData(form);
    formData.append('grade', '3'); //在发送之前添加附加数据
    var xhr = new XMLHttpRequest();
    xhr.open('POST','test.php',true);
    xhr.onload = function(e) {
        if (this.status == 200) {
            document.getElementById("result").innerHTML=this.response;
        }
    };
    xhr.send(formData); 
}

发送二进制文件

使用 FormData 可以向服务器发送文件。

具体做法:

  • 将表单 enctype 设置为 multipart/form-data。
  • 然后将需要上传的文件作为附加数据添加到 FormData 对象中即可。
/*
    <form id="form1" enctype="multipart/form-data">      
        选择文件<input type="file" id="file1" name="file" multiple><br/>
        <input type="button" value="发送" onclick="uploadFile();">     
    </form>     
    <output id="result" ></output>     
*/
function uploadFile() {
    var formData = new FormData();
    var files=document.getElementById("file1").files;
    for (var i = 0;i<files.length;i++) {
        var file=files[i];
        formData.append('myfile[]', file);
    }
    var xhr = new XMLHttpRequest();
    xhr.open('POST','test.php', true);
    xhr.onload = function(e) {         
        if (this.status == 200) {
            document.getElementById("result").innerHTML=this.response;
        }
    };
    xhr.send(formData);   
}

发送 Blob 对象

所有 File 对象都是 Blob 对象,因此可以通过发送 Blob 对象的方法来发送文件。

/*
  复制当前页面中所有代码创建一个 Blob 对象,然后通过将该 Blob 对象指定为 XMLHttpRequest 对象的 send() 方法的参数值的方法向服务器发送该 Blob 对象,服务器接收到该 Blob 对象后将其保存为一个文件,同时显示进度条
*/
/*
  <input type="button" value="复制文件" onclick="uploadDocument()"><br/>    
  <progress min="0" max="100" value="0" id="progress"></progress>
  <output id="result"/>
*/
window.URL = window.URL || window.webkitURL;
//复制当前页面
function uploadDocument(){
    var bb= new Blob([document.documentElement.outerHTML]);
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'test.php?fileName='+getFileName(), true);
    var progressBar = document.getElementById('progress');
    xhr.upload.onprogress = function(e) {
        if (e.lengthComputable) {
            progressBar.value = (e.loaded / e.total) * 100;
            document.getElementById("result").innerHTML = '已完成进度:'+progressBar.value+'%'; 
         }
    }
    xhr.send(bb);
}
//获取当前页面文件的文件名 
function  getFileName(){        
    var   url=window.location.href;             
    var   pos=url.lastIndexOf("\\");
    if   (pos==-1)     //pos==-1表示为本地文件       
        pos=url.lastIndexOf("/");   //本地文件路径分割符为"/"      
    var   fileName=url.substring(pos+1);   //从url中获得文件名    
    return fileName;      
}    

- Book Lists -