* 何为自定义协议
浏览器嘛,最重要的就是对HTTP协议的解析支持,http开头的即是。
但是也有其他协议浏览器可以进行支持,例如IE浏览器支持ftp协议。还有Chrome浏览器的设置界面chrome://等。
chromium本身就支持二次开发人员扩展自己的自定义协议,示例代码中就加入了两种自定义协议,search://和client://。
示例代码中,输入client://路径会打开一些本地资源文件,我们不介绍这个,大家可以看看。而输入search://接关键字会打开google搜索,我们着重介绍这个自定义协议,因为这里面涉及一个PunyCode/IDN的问题。
* 从search://看自定义协议
示例代码中search://后面的关键字会放到google中搜索,由于大陆访问不了google,我们使用baidu搜索。
类tests.detailed.handler.SearchSchemeHandler中38行处。
newUrl = "http://www.baidu.com/s?wd=" + newUrl;
1. 说在前面
虽然我没有把注册、拦截自定义协议的详细步骤写出来,但示例代码中体现地很完整。
首先重载org.cef.handler.CefAppHandlerAdapter.onRegisterCustomSchemes函数使用org.cef.callback.CefSchemeRegistrar.addCustomScheme注册协议名称;然后重载org.cef.handler.CefAppHandlerAdapter.onContextInitialized函数为协议注册工厂类;示例代码中从类tests.detailed.handler.AppHandler的39和57行(这个类在上一讲启用Flash中改过)
示例代码中对于工厂类、实例的管理以及对应CefBrowser/CefRequest的对应关系没有深究,但是对处理函数的线程同步等操作,包括协议头和内容分离的思想需要注意。
2. Punycode问题
我们启动调试,在地址栏输入search://java-cef,它的搜索结果完全正确;但是如果输入中文,例如search://浏览器,则会出现意想之外的效果。。。
首先,这个问题不是因为编码(中文到unicode之类的),而是因为一种叫做智能域名(IDN)的机制。以search://浏览器为例,我们search://是协议,而浏览器是域名;跟http://www.baidu.com中两个部分类似。智能域名的一个作用就有把非拉丁语系的域名转为拉丁语系域名(域名只支持ISO8859-1的ASCII编码)。因此我们的中文就被转换为xn--开头的一串英文了。
验证的方式很简单,你搜索search://浏览器/谷歌,谷歌二字不会被转义,因为它不是域名了。
我们使用Java提供的java.net.IDN.toUnicode函数可以从已被转义的字符转回来,再放到百度进行搜索就是对的了。
@Override
public boolean processRequest(CefRequest request,
CefCallback callback) {
// cut away "scheme://"
String requestUrl = request.getURL();
String newUrl = requestUrl.substring(scheme.length()+3);
// cut away a trailing "/" if any
if (newUrl.indexOf('/') == newUrl.length()-1) {
newUrl = newUrl.substring(0, newUrl.length()-1);
}
try {
URI tempUri = new URI(requestUrl);
String asciiDomain = tempUri.getHost();
String unicodeDomain = IDN.toUnicode(asciiDomain);
newUrl = newUrl.replace(asciiDomain, unicodeDomain);
} catch (URISyntaxException e) {
}
newUrl = "http://www.baidu.com/s?wd=" + newUrl;
CefRequest newRequest = CefRequest.create();
if (newRequest != null) {
newRequest.setMethod("GET");
newRequest.setURL(newUrl);
newRequest.setFirstPartyForCookies(newUrl);
browser_.loadRequest(newRequest);
}
return false;
}
上文中使用try-catch围绕的代码(用到了URI类和IDN类)即是我新添加的代码。