如果客户提出一个需求要在网页打开计算器,你可千万别以为是很简单的事,事实上,如果不借助“外力”是根本办不到。
背景
某项目客户提了这么一个需求
- 需要在chrome浏览器中打开IE和firefox
- 需要在firefox中打开IE和chrome
- 需要在IE中打开firefox和chrome
总结下就是能在任意浏览器中打开任意浏览器,乍听之下觉得很扯是不是,觉得是个傻逼需求是不是,事实上,我的第一反应也是这样的,但了解事情背景后发现这是个很合理的需求,背景是客户有几个个遗留的老系统,其中核心业务系统只能运行在IE上,某BI系统只能运行在firefox上,你可能觉得很奇怪,只支持IE这个可以理解,只支持firefox这个有点匪夷所思,但事实确实如此,我们也不必去深究,现在客户要做门户,需要在门户上单点到所有系统,那么就有了上面的需求。
沙盒模型
想象一下如果javascript能读写本地文件会发生什么事
- 打开百度你的资料可能就全部泄漏了
- 打开百度可能就给你悄悄装上全家桶了
- 打开百度你的盘可能就被格式化了
- .....
这里的javascript不是指运行在server端的nodejs javascript,指的是运行在网页端的javascript
如果真的是这样,那么互联网也不可能存在这么久,因为网站是公开的,任何人可以访问的,你的手机电脑是私人的,私密的,你不能让一个公开性质的网站去访问你私人的设备,但我们经常听说不要随便打开某某网页,小心中毒是什么回事呢,打开网页不会中毒,但网页会引导甚至自动下载病毒然后诱导你去运行病毒,比如某度全家桶,运行后导致电脑中毒,也就是说就算你浏览不良网站,只要不手贱去下载来历不明的程序就不会有问题。
那么保证浏览器不做坏事的核心就是沙盒模型理念(sandbox)也称沙箱,沙盒模型规定
- 浏览器不能访问本地程序
- 浏览器不能读本地文件
- 浏览器不能写本地文件
- 浏览器不能获取计算机敏感信息,比如用户名,email等,但可以访问一些非敏感信息,比如操作系统版,浏览器内核信息
简单说就是,不能读,不能写,不能看。在电影<<楚门的世界>>中,楚门所在的那个小镇就是一个沙盒,楚门无法接触外面的世界,他感觉不到他的世界是虚假的。有了沙盒模型,我们才能愉快的上网冲浪,享受互联网带来的便利。
如何打开本地程序
那么你可能会问,那为什么网页可以打开迅雷,打开qq这些程序呢,以迅雷为例,当我们在网页上点击迅雷下载
时系统会打开本地迅雷程序进行下载,这个是怎么办到的呢,如果仔细观察就会发现迅雷的下载地址比较特殊
thunder://QUFodHRwOi8veDEwOC55c2JpcmQuY29tOjEwNy9kYXRhL3d3dy55c2JpcmQuY29t07DK08TxL7jWzPrPwC1CbHUlNUIxMjgweDcyMCU1RC5ybXZiWlo=
他不是我们常见的http协议,协议名是thunder
,这个就是一个自定义协议,当操作系统配置了自定义协议和应用程序的关联关系后,浏览器就能根据这个配置打开相应的程序,在windows中,这个配置关系是在注册表中配置的,开篇提到的需要借助外力才能实现浏览器打开本地程序指得就是这个,下面就是迅雷thunder协议在注册表中的配置
注册表配置
比如我们需要实现在chrome中打开ie,我们规定协议ie://
,只需按照步骤配置注册表即可
- win+r打开运行窗口,输入regedit进入注册表
- 在
HKEY_CLASSES_ROOT
下创建目录ie\Shell\Open\command
(参考上图迅雷注册表) - 修改command默认值为
iexplore
- 在ie项目下创建一个字符串,名称为
URL Protocol
值为空
编写html代码
打开IE
浏览器点击后弹出窗口,点击打开就可以打开IE
当然你也可以将以下脚本保存为reg后缀的文件,直接点击就可以导入注册表,效果和你手动配置的是一样的
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\ie]
"URL Protocol"=""
@="iexplore"
[HKEY_CLASSES_ROOT\ie\shell]
[HKEY_CLASSES_ROOT\ie\shell\open]
[HKEY_CLASSES_ROOT\ie\shell\open\command]
@="iexplore"
如果你在windows控制台输入start iexplore就能打开ie,同样,start firefox打开火狐,start chrome打开chrome,star命令能够在指定文件夹搜索应用程序匹配并打开,可以参考 这里
那这样是不是就解决了客户的需求了,我们只需配几个自定义协议就可以了,答案是不可以,因为这种方式无法指定打开的url,虽然可以通过ie://http://definesys.com
指定参数,但是ie会打开原始地址,也就是ie://http://definesys.com
而不是http://definesys.com
,所以我们需要编写一个中间程序完成解析url打开指定应用。
开发应用
因为是windows应用,而且是需要安装在客户电脑上,所以java就不用考虑了,有以下选型
- windows GUI程序
- windows console程序
- .NET
console会弹出一个黑窗,体验不好,.NET对运行环境有要求,很难做到兼容,所以考虑用C语言开发windows GUI程序,功能很简单,就是解析url打开指定应用,既然是开发windows c,那么就要祭出上古神器vc++6.0
看到这熟悉又亲切的启动画面,我忍不住发了条朋友圈,祭奠那死去的青春。核心代码如下
#include "stdafx.h"
#include "resource.h"
#include "Windows.h"
#include "shellapi.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow) {
if(lpCmdLine==NULL||strlen(lpCmdLine)==0) {
MessageBox(NULL, "无法打开应用程序 原因:缺少参数", "提示", 0);
return 0;
}
lpCmdLine=lpCmdLine+7;
lpCmdLine[strlen(lpCmdLine)-1]='\0';
char *p = strchr(lpCmdLine, ':');
int size=p-lpCmdLine;
char protocal[30];
memcpy(protocal,lpCmdLine,size);
protocal[size]='\0';
char url[1000];
memset(url,'\0',1000);
strcpy(url,p+1);
int ret=(int)ShellExecute(NULL, "open", protocal, url, NULL, SW_MAXIMIZE);
if(ret<32) {
if(ret==2) {
MessageBox(NULL, strcat("启动失败没有安装",protocal), "提示", 0);
} else {
MessageBox(NULL, "启动失败未知原因请联系管理员", "提示", 0);
}
}
return 0;
}
代码做了以下内容
- 解析参数,这里规定格式为
sso://{浏览器}:{url}
比如sso://firefox:http:definesys.com
- 打开浏览器
- 如果没有安装浏览器提示
打包应用
应用开发好了,但如何交付到客户手上,不可能让每个客户都去配置下注册表,我们需要一个安装包,客户点击下一步就能完成所有安装工作的安装包,强烈推荐advancedinstaller这个安装包制作工具,我到现在才知道原来安装程序是用工具做出来的,我以为都是自己写的,advancedinstaller可以制作一个安装程序,引导用户完成安装,advancedinstaller功能很强大,其中包括了读写注册表功能
- 下载并安装advancedinstaller
- 创建一个Simple项目即可
- 配置应用信息
- 在Files and Folders中上传应用程序
- 配置注册表信息
command后面默认值为
[APPDIR]sso.exe "%1"
,APPDIR表示安装路径,名称放空就会创建一个Default的value
还需要在sso
下创建一个URL Protocol
的值
- 点击左上角Build就生成一个安装程序
测试
编写以下html用于测试
浏览器测试
浏览器测试
其中check是检测是否配置了自定义协议,其中protocolcheck.js是检测自定义协议工具包,可以从这里下载