目录
ActiveX是我们在做Web开发使经常使用的浏览器端技术,通过ActiveX控件,Web页面可以实现与计算机本地资源交互,实现特定功能。但实际上ActiveX控件技术是微软自家独有的Web页面扩展技术,它并不包含在W3C制定的HTML标准里面,所以也只有IE或使用IE内核的浏览器才支持它。。究其原因,ActiveX本质上是与Web应用的设计初衷相违背的,它模糊了客户端与服务端的边界,带来安全性、稳定性、兼容性和执行效率等问题。这也是IE在主流浏览器性能排名中总是排在后面的重要原因。但没有办法,由于Windows在桌面操作系统的统治地位,造成了ActiveX成为了事实上的行业标准,ActiveX控件技术一直是Web开发时实现本地资源交互功能的首选技术。
但随着技术和市场的变化,现在开发Web应用时要求支持Chrome等非IE浏览器已经成了标配,而这些浏览器又不支持ActiveX控件,那么需要页面与本地交互时用到有什么方法可以代替呢?今天就根据本人的实际开发经验和大家交流一下。另外,下面谈到的内容还是在桌面使用Windows系统的前提下。
一、NPAPI或PPAPI
说到上面那个问题,有经验的大神会说用NPAPI或PPAPI啦。没错,NPAPI或PPAPI是指Plugin API,即插件接口规范。按照这些规范开发的插件,就可以集成在浏览器里,然后Web页面再调用插件的接口,完成相应的功能。NPAPI/PPAPI与ActiveX控件实现的效果类似,但走的技术路线并不同。ActiveX控件修改的是Web页面,而NPAPI/PPAPI实质上通过修改浏览器实现特定的拓展功能。顺便说一下,其实IE也有类似的拓展开发接口。
NPAPI是由网景公司开发的,但因为NPAPI有很大的安全隐患,谷歌在其基础上开发了PPAPI,并且从Chrome 52版本后就不再支持NPAPI了。从技术上讲,使用PPAPI貌似应该是最正统的方案,但实际上,目前我看到使用了PPAPI的产品只有Adobe的Flash Player,可能是我孤陋寡闻,不过也从一个侧面说明了这项技术至少在国内,用的还是很少。
PPAPI上使用了沙箱技术,插件程序全部运行在沙箱里,在增强安全性的同时也带来了资源消耗的增长。另外,沙箱的诸多安全限制对于做密码应用开发,比如调用USB Key进行数字签名,会造成不小的困难。,而且现在Chrome已经不允许安装非商店的扩展了,也就是说,即使你基于PPAPI开发出了扩展插件程序,也想要上线到谷歌商店才能使用。综合以上几点,除非你的客户有特殊原因或你有一个强大的开发团队,或者你想做Flash这样级别的产品,不推荐使用NPAPI或PPAPI作为ActiveX控件的替代技术。
二、伪协议
相比高大上的PPAPI,伪协议就很接地气了。它利用浏览器打开HTTP协议的原理, “伪造”一种类似的协议,而这个协议实际上是与本地的某个可执行程序绑定。当浏览器访问以这个协议开头的URL时,就会打开绑定的本地可执行程序,实现页面与本地资源的交互。以一个打开PDF文档的页面为例子,在注册表设置以下键值:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\pdffile]
"URL Protocol"=""
[HKEY_CLASSES_ROOT\pdffile\shell]
[HKEY_CLASSES_ROOT\pdffile\shell\open]
[HKEY_CLASSES_ROOT\pdffile\shell\open\command]
@="\"D:\\eSealsPdf\\PdfReader\\Release\\PdfReader.exe\" %1"
它设置了一个名为“pdffile”的伪协议,这个伪协议与本地的PDF浏览器程序PdfReader.exe绑定。然后在Chrome等浏览器中访问如下URL:
"pdffile://www.neeq.com.cn/disclosure/2018/2018-10-09/1539075385_982462.pdf",浏览器会弹出如下提示(根据浏览器的不同提示会有所变化):
点击“打开PDFReader.exe”后,浏览器会启动程序,打开PDF文档,如下:
由此可见,伪协议的使用还是比较简单的。像我们熟悉的QQ,迅雷都采用这种方式在网页启动程序。但由于伪协议本质上打开一个程序,所以使用上还是会存在一些问题:首先是本地程序是单独启动的,因此从用户视角来看,程序窗体是弹出而不是嵌入在页面中的,这一点可能会使很多用户觉得不习惯,造成用户使用体验不好。二是程序与页面之间的数据交互问题。在上面的例子中,程序的输入参数可以由伪协议的URL传入,但如果需要程序将执行结果或输出参数返回给页面,就可能要费些周折。比如可以通过长链接轮询的方式,本地程序将执行结果发送并通知Web后台,后台再刷新前端页面。这些都无疑会增加开发的难度和工作量。
三、本地服务
在这几个方法中,本地服务是和ActiveX控件技术最契合的。说白了,它是通过开发一个本地的Web服务程序代替ActiveX控件,将原来ActiveX控件的COM接口变成本地的Web服务接口,而原来调用ActiveX控件接口的Java Script改造成调用Web服务接口的Java Script.
本地Web服务的开发方式有很多选择,一般是采用监听本地端口,对接受到的HTTP请求进行处理,并将处理的结果返回给页面。可以理解成开发一个简化版的HTTP服务器。
比如可以使用Winsock控件来监听端口,然后在控件的DataArrival消息处理函数里对接收到的数据进行处理。根据接收的数据执行相应的功能,最后将执行的结果构造成HTTP Response,再调用Send方法返回给调用方。
调用页面相应做如下改造,以实现数字签名为例:
function signmsg(msg, cert,type,callback) {
//先声明一个异步请求对象
var xmlHttpReg = null;
if (window.ActiveXObject || "ActiveXObject" in window) {//如果是IE
xmlHttpReg = new ActiveXObject("Msxml2.XMLHTTP");
} else if (window.XMLHttpRequest) {
xmlHttpReg = new XMLHttpRequest(); //实例化一个xmlHttpReg
}
//如果实例化成功,就调用open()方法,就开始准备向服务器发送请求
if (xmlHttpReg != null) {
if (!(window.ActiveXObject || "ActiveXObject" in window))
xmlHttpReg.onload = function () {
if (xmlHttpReg.status == 200)
callback(xmlHttpReg.responseText);
}//
//8888为监听端口
xmlHttpReg.open("post", "http://127.0.0.1:8888/Sign.method", false );
msg = msg.replace(/[\r\n]/g, "");
cert = cert.replace(/[\r\n]/g, "");
if (type == undefined)
msg = "msg: " + encodeURIComponent(msg) + "\r\ncert: " + cert + "\r\nend";
else
msg = "msg: " + encodeURIComponent(msg) + "\r\ncert: " + cert + "\r\nsigntype: " + type + "\r\nend";
//调用Web服务
xmlHttpReg.send(msg);
//得到服务返回数据
return xmlHttpReg.responseText;
}//
}//...
上面的代码也很好懂,就不再解释了。
使用这种方案也有其自身问题:一是本地服务启动问题。本地服务可以做成自启动,也可以做成Windows服务,不过这可能都会面临被一些安全软件,如360,认为是不安全程序而禁用的情况,这时就需要加入安全软件的白名单,这些无疑会带来使用上的门槛和不便。比较好的办法是在页面中需要调用Web服务接口之前,使用伪协议来启动本地服务程序,这样可以做到按需启动,降低被拦截的可能性。二是页面中的脚本需要访问本地服务,如上面的“http://127.0.0.1:8888/Sign.method”。这时如果Web页面的URL是由HTTPS开头的,部分浏览器可能会认为HTTP开头的本地服务不安全,因此会禁止脚本访问。当然,你可以开发一个支持HTTPS的本地服务,然后以HTTPS的方式访问。但开发这样的本地服务,并不是简单的事情。幸好,目前我只发现QQ浏览器存在这样的问题。当然,这个也并不是我们常说的跨域问题。
从开发成本和使用效果来讲,使用本地服务的方式还是比较合适的选择。对于已采用ActiveX控件的页面,可以比较方便的改造成使用本地服务。进一步说,即使桌面系统不是Windows,也可以采用这种本地服务的方法,而且已经实现调用服务的页面脚本可以不用做任何改造。
四、总结
综上所述,本文建议一般情况下,对于非IE浏览器,应采用本地服务或本地服务+伪协议的方式来替代ActiveX控件技术。