你的位置:首页 > Java教程

[Java教程]第十六章:脚本化HTTP


写在本章内容前:

第十五章:事件处理 涉及到到较多的文字篇幅,介于个人精力问题,暂不更新。主要包含的内容有事件类型、注册事件处理程序、事件处理程序的调用、文档加载事件、鼠标事件、鼠标滚轮事件、拖放事件、文本事件、键盘事件等9块内容。感兴趣的朋友可以留言传内容PDF。如果不着急的话,后期可能更新。敬请关注。

超文本传输协议(HTTP)规定web浏览器如何从web服务器器获取文档和向web服务器发送表单内容,以及web服务器如何响应这些请求和提交。web浏览器会处理大量的HTTP。通常,HTTP并不在脚本的控制下,只是当用户单击链接、提交表单和输入URL时才发送。

但是,用javascript代码操纵HTTP是可行的。当脚本设置window对象的location属性或调用表单对象的submit()方法时,都会初始化HTTP请求。在这种情况下,浏览器会从新加载页面。这种用脚本控制HTTP的方法在多框架页面中非常有用,但这并非我们讨论的主题。相反,这章讨论在没有导致web浏览器重新加载任何窗口或窗体内容的情况下,脚本如何实现浏览器与服务器之间的通信。

术语Ajax描述了一种主要使用脚本操作HTTP的web应用构架。(Ajax是Asynchronous javascript and “Ajax”曾经是一个流行多年的术语,现在它只不过是一个有用的术语,来描述脚本操纵HTTP请求的web应用构架)。Ajax应用的主要特点是使用脚本操作HTTP和web服务器进行数据交换,不会导致页面重载。避免页面重载(这是web初期的标准做法)的能力能使web应用感觉更像传统的桌面应用。web可以使用Ajax技术把用户的交互记录数据记录到服务器中;也可以是简单的显示页面,之后按需加载额外的数据和页面组件来提示应用的启动时间。

Comet是和使用脚本操作HTTP的web应用构架相关的术语(Comet这个名字是Alex Russell在2006年3月创造,这个名字可能是对Ajax开了个玩笑,Comet和Ajax都是美国的洗涤日用品牌)。在某种意义上,Comet和Ajax相反,在Comet中,web服务器发起通信并异步发送到消息客户端。如果web应用需要相应服务器发送消息,则它会使用Ajax技术发送或请求数据。在Ajax中,客户端从服务器“拉”数据。在Comet中,服务端向客户端“推”数据。Comet还包括其他名词,如:“服务器推”,“Ajax推”,“HTTP流”。

实现Ajax和Comet的方式有很多种,而这些底层的实现有时候称为传输协议(transport)。例如:<IMG>元素有一个src属性。当脚本设置这个属性为url时,浏览器发起的HTTP请求会从这个URL下载图片。因此脚本通过设置<img>元素的src属性,且把信息图片URL的查询字符串部分,就把能经过编码的信息传递给web服务器。web服务器实际上必须返回某个图片作为请求结果,但它一定要不可可见。例如一个1*1像素的透明图片(这种类型的图片也叫网页信标(web bug)当网页信标不是与当前网页服务器而是其它服务器交流信息时,会担心隐私泄露。这种第三方的网页信标方式常用于统计点击数和网站流量分析)。

<img>元素无法实现完整的的Ajax传输协议,因为数据交换是单向的:客户端能发送数据到服务器,但服务器的响应一直是张图片导致客户端无法从中获取提取信息。然而<iframe>元素更加强大,为了把<iframe>作为Ajax传输协议使用,脚本首先要把发送给web服务器的信息编码到URL中,然后设置<iframe>的src属性为该URL。服务器创建一个包含响应内容的HTML文档。并把它返回给web浏览器。并且在<iframe>中显示它。<iframe>需要对用户不可见。可以使用css隐藏它。脚本能遍历<iframe>的文档对象来读取服务端的响应,注意,这种方法受限于11.6.ii介绍的同源策略问题。

实际上,<script>元素的src属性能设置URL并发起HTTP GET请求,使用<script>元素实现脚本操纵HTTP是非常吸引人的。因为它们可以跨域通信并不受限于同源策略。通常,使用<script>的Ajax传输协议时,服务器的响应采用JSON(见6章9节)的数据格式,当知心脚本时,javascript解析器也能自动将其“解码”。由于它使用JSON数据格式,因此这种Ajax传输协议也叫“JSONP”。

虽然在<iframe>和<script>传输协议上能实现AJAX技术,但通常还有更简单的方式,一段时间来,所有的浏览器都支持,同时它能用文本或Document对象的形式返回服务器响应。虽然它的名字叫本章第1节涵盖。

本章的大部分Ajax示例都将使用(第1节)方案,我们也将在本章第2节演示如何基于<script>的传输协议。因为<script>有规避同源限制的能力。

“Ajax”中的X表示当然AJax吉祥能和:

 对象名

Comet传输协议比Ajax更精妙,但需要客户端和服务器建立(必要时重新建立)连接。同时要保存服务器连接处于打开状态。这样才能发送异步信息。隐藏的<irrame>能像Comet传输协议一样有用。例如:服务器以<iframe>中执行的<script>元素的形式发生每条消息。实现Comet的一种更可靠的跨平台方案是客户端简历一个服务器连接(使用Ajax协议),同时服务器包装这个连接打开直到它需要推送一条信息,服务器每发送一个信息就关闭这个连接。这样就可以确保客户端正确接收到消息。处理该消息之后,客户端马上为后续的消息推送建立一个新连接。

实现可靠的跨平台Comet传输协议是非常有挑战性的,所以大部分使用Comet构架的web应用开发者依赖像Dojo这样的web框架中的传输协议。HTML5相关草案的Server-Sent事件,它用EventSource对象的形式定义了简单的Comet API。本章第3节涵盖EventSoure API并且演示了一个使用

在Ajax和Comet之上构建更高的通信协议是可行的。例如,这些客户端/服务器技术可以用作RPC(Remote Procedure Call,远程过程调用)机制或发布/订阅事件系统基础。

本章不会介绍更高的协议,我们的重点在能使Ajax和Comet可用的API上。

1.使用

浏览器在独立的请求/响应对。并且这个对象的属性和方法允许指定请求细节和提取响应数据。很多年前web浏览器就开始支持同时W3C在制定“2级本节涵盖。也包括当前至少被两款浏览器支持的2级

当然,使用这个HTTP API做的第一件事就是实例化

      var request = new 

你也可以重用已存在的,但注意这将会终止之前通过该对象挂起的任何请求。

IE6中的

微软最早把

       //ie5和i6模拟      if (window. undefined) {        window.= function() {          try {            //如果可用,则使用Active对象的最新版本            return new ActiveXObject("Ms);          } catch (e1) {            try {              //否则回退早的版本              return new ActiveXObject("Ms);            } catch (e2) {              //否则,抛出错误              throw new Error(");            }          }        };      }

一个HTTP请求由4个部分组成:

  • HTTP请求的方法或“动作”(verb)
  • 正在请求的URL
  • 一个可选的请求头集合。其中可能包含身份验证
  • 一个可选的请求主体

服务器返回的HTTP请求响应包含3个部分:

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

接下来两节我们将会展示如何设置HTTP请求的每个部分和如何查询HTTP响应的每个部分。随后的核心章节会涵盖更多的专门议题。

HTTP的基础请求/响应构架非常简单并易用使用。但在实践中会有各种各样的带来复杂的问题:客户端和服务器交换cookie,服务器重定向浏览器到其它服务器,缓存某些资源而剩下的不缓存,某些客户端通过代理服务器发送所有的请求等。

网页中可以使用相对URL意味这我们可以使用本地文件系统来开发和测试HTML,来避免不必要的服务器端部署。然而在师院

i.指定请求

创建对象的open()方法去指定这个请求的两个必须部分:方法和URL。

    request.open("GET",//开始一个HTTP GET请求    "data.csv;") //URL的内容

open()的第一个参数指定HTTP方法或动作。这个字符串不区分大小写,但通常大家使用大写来匹配HTTP协议。“GET”和"POST"方法是广泛支持的。“GET”用于常规请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器响应是可缓存的。“POST”方法常用于HTML表单。它在请求主体中包含额外数据(表单数据),且这些数据常存储到服务器上的数据库中(副作用)。相同的URL重复“POST”请求可能从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求。

除了GET和POST之外 ,(HTTP CONNTECT TRACE TRACK因为安全风险已经被明确禁止)。旧的浏览器并不支持这些方法,但至少“HEAD”得到广泛支持,本章有例子演示如何使用它。

open()第二个参数是URL。它是请求的主题。这是相对于文档的URL,这个文档包含调用open()的脚本。如果指定绝对URL、协议、主机和端口通常必须匹配所在文档的对于内容:跨域请求通常会报错(但是在服务器明确允许跨域时,2级

如果有请求头的话,请求进程的下个步奏是设置它。例如,POST请求需要“Content-Type”头指定请求主题的MIME类型。

      request.setRequestHeader("Content-Type", "text/plain");

如果相同的头调用setRequestHeader()多次,新值不会取代之前指定的值,相反,HTTP请求将包含这个头的多个副本或这个头将指定多个值。

你不能自己指定“Content-Length”、“Date”、“Referer”或“User-Agent”头,

Accept-CharsetContent-Transfer-EncodingTE
Accepet-EncodingDateTrailer
ConnectionExpectTransfer-Encoding
Content-lengthHostUpgrad
cookieKeep-AliveUser-Agent
cookie2RefererVia

你能为请求指定“Authorization”头,但通常不需要这么做。如果请求一个受密码保护的URL,把用户名和密码作为第4个和第5个参数传递给open()可选的第三个参数。可选的用户名和密码参数会在第4部分介绍。

使用

request.send(null);

GET请求绝对没有主体,所有应该传递null或省略这个参数。POST请求通常拥有主体,同事它应该配置使用setRequestHeader()指定的“Content-Type”头。

顺序问题

HTTP请求的各部分有指定顺序:请求方法和URL首先到达,然后是请求头,最后是请求主体。

下面的例子调用了目前我们介绍的所有

       /**POST方法发送纯文本给服务器**/      function postMessage(msg) {        var request = new //新请求        request.open("POST", "log.php"); //用POST向服务器端发送脚本        //用请求主题发送纯文本消息        request.setRequestHeader("Content-type", //请求主体讲述纯文本          "text/plain;charset=UTF-8");        request.send(msg);        //请求完成 ,将忽略任何响应和错误      }

上个例子的send()方法启动请求,然后返回。当它等待的服务器的响应时间并不阻塞。接下来章节介绍的几乎都是异步处理HTTP响应。

ii.取得响应

一个完整的HTTP响应由 状态码、集合头集合和响应主体组成。这些都可以通过:

status和statusText属性以数字和文本的形式返回HTTP状态码。这些属性保存标准的HTTP值。像200和“OK”表示成功请求,404和“Not Found”表示URL不能匹配服务器上的任何资源。

使用getResponseHeader()和getAllResponseHeaders()能查询响应头。

响应主体可以从responseText属性中得到文本形式的,从response(这个属性名是有历史性的:它实际上对XHTML和

必须监听(或者4小节描述的XHR进度事件)。但为了理解这个事件类型,你必须理解readyState属性

readyState是一个整数,它指定了HTTP请求的状态。同时,下表列出了它的可能性的值。第一列符号是

常量含义
UNSENT0open()尚未调用
OPENED1open()已调用
HEADERS_RECEIVED2接收到头信息
LOADING3接收到响应主体
DONE4响应完成

理论上,每次readState属性改变都会触发readystatechange事件。实际上当reayState改变为0或1时可能没触发这个事件。当调用send()时,即使reayState仍处于OPEN状态,也通常触发它。某些浏览器在LOADING状态是能够触发多次给出进度反馈。当readyState值改变为4或服务器响应完成时,所有的浏览器都能触发readystatechange事件。因为在响应完成之前也会触发事件,所以事件处理程序应该一直校验reayState值。

为了监听readystatechange事件,请把事件处理函数设置为

下面的例子定义了getText()函数来演示如何监听reaystatechange事件。事件处理程序首先要确保请求完成。如果这样,它会检测响应状态码来确保请求成功。然后它查找“Content-Type”头来验证响应主体是否是期望类型。如果3个条件都得到满足,它会把响应主体(以文本形式)发送给指定的回调函数。

       /*获取HTTP响应的onreadysatechange*/       //发出一个HTTP GET请求以获得指定URL内容       //当响应成功到达,验证它是否是纯文本       //如果是,把它传递给指定的回调函数      function getText(url, callback) {        var request = new //创建新请求        request.open("GET", url); //指定获取URL        request.onreadystatechange = function() { //定义事件处理程序          //如果请求完成,则它是成功的          if (request.readyState === 4 && request.status === 200) {            var type = request.getResponseHeader("Content-Type");            if (type.match(/^text/)) //确保响应是文本              callback(request.responseText); //把它传递给回调函数          }        };        request.send(null); //立即发送请求      }

⑴.同步响应

由于其本身的性质,异步处理HTTP响应是最好的方式。然而,

       //发起同步的HTTP GET请求以获得指定URL的内容       //返回响应文本,或如果请求不成功或响应不是文本就报错      function getTextSync(url) {        var request = new //创建新请求        request.open("GET", url, false); //传递false实现同步        request.send(null); //立即发送请求        //如果请求不是200 OK,就报错        if (request.status !== 200) throw new Error("request.statusText");        //如果类型错误,就报错        var type = request.getResponseHeader("Content-Type");        if (!type.match(/^text/))          throw new Error("Expected texttual response: " + type);        return request.responseText;      }

同步请求是吸引人的,但应该避免使用它们。客户端javascript是单线程的,当send()方法阻塞时,它 通常导致整个浏览器UI冻结。如果连接的服务器 响应慢,那么用户的浏览器冻结。(然而,参加20章4小节可以接受使用同步的请求的场景。)

⑵.响应解码
在前面的示例中,我们假设服务器器使用像“text/plain”、“text/html”、或“text/css”这样的MIME类型发送文本响应,然后我们使用 但是还有其它方式来处理服务器的响应。如果服务器发送

如果服务器想发送诸如对象或数组这样的结构化数据作为其响应,它应该传输JSON编码(6章9节)的字符串。当接受它时,可以把responseText属性传给JSOP.parse()。下面的例子是上面的一个例子的归纳:它实现指定URL的GET请求并当URL的内容准备就绪时把他们传递给指定的回调函数。但它不是一直传递文本,而是传递Document对象或使用JSON.parse()编码对应的对象或字符串。

       /**解析HTTP响应**/       //发起http get响应以获取指定url内容       //当响应到达时,把它以解析后的      function get(url, callback) {        var request = new "GET", url); //创建新请求        request.onreadystatechange = function() { //定义事件监听器          //如果请求完成且成功          if (request.readyState === 4 && request.status === 200) {            //获取响应的类型            var type = request.getAllResponseHeaders("Content-Type");            //检测类型,这样我们不能再将带得到HTML文档            if (type.indexOf(" request.response//Document对象响应            else if (type === "application/json")              callback(JSON.parse(request.responseText)); //JSON响应            else              callback(request.responseText); //字符串响应          }        };        request.send(null); //立即发送      }

 上面的该响应的“Content-Type”头且专门处理“application/json”影响。你可能希望特殊的编码的另一个响应是“application/javascript”或“text/javascript”。你能使用要记住,<script>元素能发起跨域HTTP请求,而。

web服务端通常使用二进制数据(比如图片文件)响应HTTP请求,responseText属性只能用于文本,且它不能妥善处理二进制响应,即使对最终字符串使用了charCodeAt()方法,XHR2定义了处理二进制响应,即使对最终字符串使用了CharCodeAt()方法。XHR2定义了处理二进制响应的方法。

服务器响应的正常解码是假设服务器为这个响应发送了"Content-Type"头和正确的MIME类型。例如,如果服务器发送了XHR2定义了overrideMimeType()方法来解决这个问题,并且大量的浏览器已经实现了它。如果想对服务器你更了解资源的MIME类型,那么在调用send()之前把类型传递给overrideMimeType()。这将使

      //不要把响应作为      request.overrideMimeType("text/plain;charset=utf-8")

iii.编码请求主体

HTTP POST请求包含一个请求主体,它包含客户端传递给服务器的数据。在postMessage()例子中,请求主体是简单的文本字符串,但是我们通常使用HTTP请求发送的都是更复杂的数据。本节演示这样做的一些方法。

⑴表单编码的请求

考虑HTML表单。当用户提交时,表单中的数据(每个表单元素的名字和值)编码到一个字符串中并随请求发送。默认的情况下,HTML表单通过POST方法发送给服务器,而编码后的表单数据则用做请求主题。对表单数据使用的编码方案相对简单:对每个表单元素的名字和执行普通的URL编码(使用十六进制转义码替换特殊字符串),使用等号后编码后的名字和值分开,并使用“&”符号分开名/值对。一个简单的编码如下这样:

       find=laobeijing&mendian=3123&radius=1km

表单数据编码格式有一个正式的MIME类型

    application/x-www-form-urlencoded

当使用POST方法提交这种顺序的表单数据时,必须设置"Content-Type"请求头为这个值。

注意。这种类型的编码并不需要HTML,在本章我们实际上将不需要直接使用表单。在Ajax应用中,你希望发送给服务器的很可能是一个javascript对象。(这个对象可能从HTML表单的用户输入中得到,但这里不是问题),前面展示的数据变成javascript对象的表单的编码形式可能是:

      {        find: "laobeijing",        mendian: 3123,        radius: "1km"      }

表单编码在web上是如此广泛使用,同时所有服务器端的编程语言都能得到良好的支持,所有非表单的数据的表单编码通常也是容易实现的事情。下面的例子展示了如何实现对象属性的表单的编码。

       /**用于HTTP请求的编码对象**/      /**       * 编码对象的属性       * 如果它们是来自HTML表单的名/值对,使用application/x-www-form-urlencode格式       **/      function encodeFormDate(data) {        if (!data) return ""; //一直返回字符串        var pair = []; //为了保存名=值对        for (var name in data) { //为了每个名字          if (!data.hasOwnProperty(name)) continue; //跳过继承属性          if (typeof data[name] === "function") continue; //跳过方法          var value = data[name].toString(); //把值转化为字符串          name = encodeURIComponent(name.replace("%20", "+")); //编码名字          value = encodeURIComponent(value.replace("%20", "+")); //编码值          pair.push(name + "=" + value); //记住名对        }        return pair.join("&"); //返回使用“&”连接的名/值      }

使用已经定义的encodeFormData()函数,我们能容易的写出像下面的例子中的postData()函数这样的工具函数,需要注意的是,简单来说,postData()函数(在随后的示例中有相似的函数)不能处理服务器响应。当响应完成后,它传递整个

       /**使用表单编码数据发起一个HTTP POST请求**/      function postData(url, data, callback) {        var request = new "POST", url);        request.onreadystatechange = function() { //简单的事情处理程序          if (request.readyState === 4 && callback) //当响应完成            callback(request); //调用回调函数        };        request.setRequestHeader("Content-type", //设置Content-type          "application/x-www-form-urlencoded");        request.send(encodeFormData(data)); //发送表单编码数据      }

表单数据同样可以通过GET请求来提交,既然表单提交的目的是为了执行只读查询,因此GET请求比POST更合适。(当提交表单的目标仅仅是一个只读查询,GET比POST更合适)GET请求从来没有主体,所以需要发送给服务器的表单编码数据“负载”需要一个URL(后跟一个问号)的查询部分。encodeFormData()工具函数也能用于这种GET请求。下面的例子演示了如何使用它。

       /**使用表单数据发起GET请求**/      function getData(url, data, callback) {        var request = new "GET", url +          "?" + encodeFormData(data)); //通过添加编码数据获取指定url        request.onreadystatechange = function() { //简单事件处理程序          if (request.readyState === 4 && callback) callback(request);        };        request.send(null); //发送请求      }

HTML表单在提交的时候会对表单数据进行URL编码,但使用

    http://www.a.com/01234/1km/mendian

 

(晚上精力有限,更新至此。欢迎大家关注本章内容,以下将更新)

⑵JSON编码的请求

⑷上传文件

⑸multipart/form-data请求

iiii.HTTP进度事件

上传进度事件

iiiii.中止请求和超时

iiiiii.跨域HTTP请求

2.借用<script>发送HTTP请求:JOSNP

3.基于服务器推送的Comet技术