HTTP 请求的拦截功能的应用
随着互联网应用及 B/S 架构的软件系统的风行,越来越多的基于浏览器的应用得以开发和部署。对已经上线的应用系统进行拦截和跟踪可以方便、快捷地实现很多功能。例如,
现今一些有趣的匿名代理服务器也是根据逆向代理服务器的原理实现的(如 http://www.youhide.com/),这类网站也有 URL 改写的需求;
另外,IBM 提供的在线翻译网页的服务中,为了能够让被翻译页面的链接所指向的页面在用户交互时继续得到翻译,我们同样需要使用 URL 的拦截和改写功能。
不仅仅是 URL 改写,通过拦截和跟踪技术可以在极小修改或者使用反向代理不修改原网页的前提下为基于 BS 的 Web 应用提供更复杂的页面改写,脚本改写等功能。
基本原理
如图 1 所示,传统的 Web 访问模型通过浏览器向 HTTP 服务器获取 Web 数据,浏览器将获取的数据进行解释渲染,得到用户的客户端界面。
图 1. 传统 Web 访问模型
而在带有服务器端和浏览器端跟踪和拦截的访问模型中,传统的访问方式发生了变化。服务器端和浏览器端的跟踪和拦截将页面中的拦截对象,在适当的拦截时机进行了拦截变化,导致用户面前的浏览器的解释渲染结果和 HTTP 服务器上的内容存在了逻辑上的差异,而这种差异恰巧是 HTTP Server 所需要的结果,而不方便将差异部署在 HTTP Server 上。 Server trace 可以作为反向代理服务器的一项逻辑功能存在,Browser trace 是通过脚本完成在客户端拦截和跟踪行为。
服务器端实现和客户端浏览器实现的比较
拦截根据位置可以分为服务器端和客户端两大类,客户端拦截借助 JavaScript 脚本技术可以方便地和浏览器的解释器和用户的操作进行交互,能够实现一些服务器端拦截不容易实现的功能。如果将服务器端和客户端拦截融合在一起,可以很好地处理拦截和跟踪问题。
表 1. 服务器端拦截和客户端拦截的功能比较
功能比较 | 服务器端拦截 | 客户端拦截 |
向页面头部插入代码 | 强。简洁,无需逐个加入代码 | 麻烦。需要逐个为页面加代码 |
访问资源的权限 | 强。可以访问跨域资源。 | 受限。浏览器间有差异 |
会话的控制和访问 | 强。可以使用相应 API 。 | 受限。需要使用 cookie |
页面 HTML 的拦截和跟踪 | 一般。通过正则跟踪。 | 强。可以使用 DOM 操作 |
页面脚本的拦截和跟踪 | 一般。通过正则跟踪 | 强。可以利用解释器环境 |
跟踪用户的交互行为 | 一般。通过正则跟踪 | 强。可以利用浏览器事件 |
拦截对象
拦截对象指的是需要被处理的元素,可能包括 [HTML 标签 ],[HTML 标签的属性 ],[ 脚本变量 ],[ 对象 ],[ 函数 ] 等。拦截对象 和 拦截时机 就构成了一个个非常实际的功能或用途。
功能:拦截页面中的所有链接,将其 href 属性的值 xxx 改为 http://proxyhost/agent?url=xxx
拦截对象 : <A> 标签
拦截时机:根据需要适当选择
功能:拦截页面中的 document.write 的功能,在调用此函数前修改函数参数
拦截对象 : 对象方法
拦截时机:根据需要适当选择
功能 : 拦截页面中用户自定义的函数 functionA 的调用
拦截对象: 用户自定义函数
拦截时机 : 根据需要适当选择
插入点和拦截时机
插入点: 插入点指的是拦截脚本 (JavaScript) 植入页面文件的位置。
拦截时机: 拦截时机从页面的载入过程可以分为 [ 页面加载前 ],[ 页面加载中 ],[ 页面加载后 ] 。
通常来说,在页面加载后进行处理代码相对简单,但是功能也相对受限。页面加载前处理代码实现相对复杂,但是功能相对丰富。用户可以根据需要混合使用。
从用户的角度出发,自然希望插入点简单,拦截时机和拦截点丰富。 因此本文的讨论的拦截和跟踪的技术的出发点也就明了了,即 : 在尽可能少的对原有系统的改变的前提下,通过 JavaScript 代码跟踪并改变系统的行为与内容。
拦截时机的实现
拦截时机中讲到的 [ 页面加载前 ] 是指在用户的 page 内容被浏览器解释前进行拦截的时机。这种拦截可以在用户没有看到渲染结果的时候完成拦截修改,这种技术将在第五种方法中详细介绍。
[ 页面加载中 ] 拦截通常是将代码放在页面中合适的位置,实现代码简单,但是代码的插入点 ( 也就是页面的合适位置 ) 可能阻碍了这种时机的使用频度,也违背了本文的初衷。因此 [ 页面加载中 ] 的拦截时机通常使用 [ 页面加载前 ] 的拦截时机进行替代。
例如 : 某 Web 页面的第 100 行有如下内容:
<script>function userFunction() { //do some actions }</script>
在页面的第 200 行调用了上面定义的函数,如:
<script>userFunction(); <script>
第 200 行的脚本会在页面的渲染过程中发生作用,如果希望拦截的话,就需要在第 100 行后,200 行前对 userFunction 进行覆盖,这样就会在页面的加载过程中完成拦截。这种做法严重依赖代码的插入点,因此使用效果不好。
[ 页面加载后 ] 拦截,首先在这里介绍,实现简单,能实现很多功能。
方法 : 通常在页面的尾部 </body> 标签前,加入:
<script type= "text/javascript" src="lib.js" ></script>
在 lib.js 文件中,通过改变 window.onload 的定义来构建我们的代码入口。为了保持功能,建议首先保存原来的函数的指针。典型的实现代码可以参考如下 :
var oldOnload = window.onload;
window.onload = function() {
//code to insert
if(oldOnload != null) {
oldOnload();
}
}
在文件末尾 </body> 前植入代码,是为了尽量防止其他加载代码的覆盖,如果不考虑加载过程中的代码覆盖的话,也可以考虑将代码放在页面的相对靠前的位置。
只要不是在加载过程中执行的代码和效果都可以考虑通过 [ 页面加载后 ] 这种拦截时机实现。这种代码的结构简单,可以很好的执行拦截和跟踪任务。特别是针对函数覆盖这种需求,通常情况下,onload 事件之前系统已经加载好所需要的 script 定义的函数和类,因此 onload 事件中的代码覆盖可以起到很好的作用。下面介绍的几种方法都可以首先考虑这种拦截时机。
常见用法 : 在代码的插入位置,可以植入一些 DOM 的操作,来实现页面的变化。
例如 : 遍历 DOM Tree, 修改特定 Node 的特定属性,或者在特定位置插入新的 Node 等等。
服务器端实现和客户端浏览器实现的比较
拦截根据位置可以分为服务器端和客户端两大类,客户端拦截借助 JavaScript 脚本技术可以方便地和浏览器的解释器和用户的操作进行交互,能够实现一些服务器端拦截不容易实现的功能。如果将服务器端和客户端拦截融合在一起,可以很好地处理拦截和跟踪问题。
表 1. 服务器端拦截和客户端拦截的功能比较
功能比较 | 服务器端拦截 | 客户端拦截 |
向页面头部插入代码 | 强。简洁,无需逐个加入代码 | 麻烦。需要逐个为页面加代码 |
访问资源的权限 | 强。可以访问跨域资源。 | 受限。浏览器间有差异 |
会话的控制和访问 | 强。可以使用相应 API 。 | 受限。需要使用 cookie |
页面 HTML 的拦截和跟踪 | 一般。通过正则跟踪。 | 强。可以使用 DOM 操作 |
页面脚本的拦截和跟踪 | 一般。通过正则跟踪 | 强。可以利用解释器环境 |
跟踪用户的交互行为 | 一般。通过正则跟踪 | 强。可以利用浏览器事件 |
从表 1 可以看出服务器端拦截和客户端拦截具有很强的互补性,服务器端拦截擅长服务器 - 浏览器的通信控制、资源的访问以及批量页面的操作,浏览器端拦截可以依靠 Script 解释器的环境,擅长个体页面的逻辑拦截和跟踪。本文因篇幅限制,主要针对使用 JavaScript 的 浏览器端的拦截和跟踪进行介绍。不过在进入具体的实现之前,先介绍几个术语。
拦截对象
拦截对象指的是需要被处理的元素,可能包括 [HTML 标签 ],[HTML 标签的属性 ],[ 脚本变量 ],[ 对象 ],[ 函数 ] 等。拦截对象 和 拦截时机 就构成了一个个非常实际的功能或用途。
功能:拦截页面中的所有链接,将其 href 属性的值 xxx 改为 http://proxyhost/agent?url=xxx
拦截对象 : <A> 标签
拦截时机:根据需要适当选择
功能:拦截页面中的 document.write 的功能,在调用此函数前修改函数参数
拦截对象 : 对象方法
拦截时机:根据需要适当选择
功能 : 拦截页面中用户自定义的函数 functionA 的调用
拦截对象: 用户自定义函数
拦截时机 : 根据需要适当选择
插入点和拦截时机
插入点: 插入点指的是拦截脚本 (JavaScript) 植入页面文件的位置。
拦截时机: 拦截时机从页面的载入过程可以分为 [ 页面加载前 ],[ 页面加载中 ],[ 页面加载后 ] 。
通常来说,在页面加载后进行处理代码相对简单,但是功能也相对受限。页面加载前处理代码实现相对复杂,但是功能相对丰富。用户可以根据需要混合使用。
从用户的角度出发,自然希望插入点简单,拦截时机和拦截点丰富。 因此本文的讨论的拦截和跟踪的技术的出发点也就明了了,即 : 在尽可能少的对原有系统的改变的前提下,通过 JavaScript 代码跟踪并改变系统的行为与内容。
拦截时机的实现
拦截时机中讲到的 [ 页面加载前 ] 是指在用户的 page 内容被浏览器解释前进行拦截的时机。这种拦截可以在用户没有看到渲染结果的时候完成拦截修改,这种技术将在第五种方法中详细介绍。
[ 页面加载中 ] 拦截通常是将代码放在页面中合适的位置,实现代码简单,但是代码的插入点 ( 也就是页面的合适位置 ) 可能阻碍了这种时机的使用频度,也违背了本文的初衷。因此 [ 页面加载中 ] 的拦截时机通常使用 [ 页面加载前 ] 的拦截时机进行替代。
例如 : 某 Web 页面的第 100 行有如下内容:
<script>function userFunction() { //do some actions }</script>
在页面的第 200 行调用了上面定义的函数,如:
<script>userFunction(); <script>
第 200 行的脚本会在页面的渲染过程中发生作用,如果希望拦截的话,就需要在第 100 行后,200 行前对 userFunction 进行覆盖,这样就会在页面的加载过程中完成拦截。这种做法严重依赖代码的插入点,因此使用效果不好。
[ 页面加载后 ] 拦截,首先在这里介绍,实现简单,能实现很多功能。
方法 : 通常在页面的尾部 </body> 标签前,加入:
<script type= "text/javascript" src="lib.js" ></script>
在 lib.js 文件中,通过改变 window.onload 的定义来构建我们的代码入口。为了保持功能,建议首先保存原来的函数的指针。典型的实现代码可以参考如下 :
var oldOnload = window.onload;
window.onload = function() {
//code to insert
if(oldOnload != null) {
oldOnload();
}
}
在文件末尾 </body> 前植入代码,是为了尽量防止其他加载代码的覆盖,如果不考虑加载过程中的代码覆盖的话,也可以考虑将代码放在页面的相对靠前的位置。
只要不是在加载过程中执行的代码和效果都可以考虑通过 [ 页面加载后 ] 这种拦截时机实现。这种代码的结构简单,可以很好的执行拦截和跟踪任务。特别是针对函数覆盖这种需求,通常情况下,onload 事件之前系统已经加载好所需要的 script 定义的函数和类,因此 onload 事件中的代码覆盖可以起到很好的作用。下面介绍的几种方法都可以首先考虑这种拦截时机。
常见用法 : 在代码的插入位置,可以植入一些 DOM 的操作,来实现页面的变化。
例如 : 遍历 DOM Tree, 修改特定 Node 的特定属性,或者在特定位置插入新的 Node 等等。
结论
通过上面的介绍,可以看出发挥 JavaScript 的优势,我们能够很好的控制页面的加载过程,在不需要二进制浏览器插件的情况下,仅仅通过脚本对 Web 内面的逻辑拦截是可行的。这种方法在反向代理、网站的改版重构、以及不方便对原有内容进行直接改动的场合是十分有益的