Adobe AIR实现支持代理的HTTP请求

也是因为那个采集项目的需要. 很失落地发现Adobe AIR的XMLHttpRequest也好, URLLoader也好, 都不支持设置代理, 一般似乎是用的系统设置. 于是自己实现了一个, 比较简单, 不支持HTTPS, 但是cookie什么的都是做到位了的. 有responseText和responseBody, 分别用于读取文本和字节文件, 如图片. 暂不支持压缩, 不过chunked是支持的.

代码已经传到github上了, 这里上一段实例, 当然, 只用到少数功能. 顺便这个是基于VEJIS, 如果不想或者不会用VEJIS可以自己稍作修改.

这里是VEJIS https://github.com/vilic/vejis
这里是这个HTTP请求 https://github.com/vilic/air-proxy-enabled-http-request-for-js

use_("http-request", function (hr) {
    var req = new hr.Request();

    //you can turn off cookies
    //req.cookieEnabled = false;
    //or turn off auto redirect
    //req.autoRedirect = false;

    req.proxy.host = "localhost";
    req.proxy.port = 1107;

    req.open("get", "http://www.vilic.info/blog/");

    //you can set request headers
    //req.setRequestHeader("Referer", "http://www.vilic.info/");

    req.send(function (req) {
        if (req.error) {
            alert(req.error);
            return;
        }

        alert(req.status);
        alert(req.responseText);
    });

    //if you use post, you'll also need to send the data
    //string and ByteArray are supported
    //req.send(data, callback);
});

Adobe AIR JavaScript跨域获取iframe内容

老实说在想到这个解决方案之前, 超级吓, 因为不解决这个问题, 就意味着项目无法实现预期的功能.

Adobe AIR中跨域的问题貌似主要是因为安全沙箱(security sandbox)不同导致的, 官方提供了两个相关的解决方案, 一个是使用iframe的sandboxRoot和documentRoot属性, 如:

<iframe
    src="http://www.example.com/local/ui.html"
    sandboxRoot="http://www.example.com/local/"
    documentRoot="app:/sandbox/">
</iframe>

我花了很长很长的时间才理解这两个属性到底指什么, 一方面是因为官方文档说得不够清楚, 我一开始一直以为app:/sandbox/是表示应用沙箱的固定用法, 一方面也是因为相关资料相对较少.

这个方法使得跨域页面中的内容(不包括iframe的src, 但包括iframe加载的内容里的各种东西, 甚至是XMLHttpRequest), 如果其URL在sandboxRoot指定的URL之下, 则并不会请求该URL在互联网上的位置, 而是请求documentRoot所指定的文件目录中的内容. 比如按上面的例子, 如果iframe加载的页面中有一个脚本的URL是http://www.example.com/local/sample.js, 则实际会加载的则是app:/sandbox/sample.js, 这里的documentRoot可以是任意目录.

但是这个方案似乎只能说是加载下文件, 并不能让脚本获取iframe的内容. 另外则有两个bridge(桥), 可以实现脚本的交互. 一个是parentSandboxBridge, 另一个是childSandboxBridge. 这两个bridge都是iframe.contentWindow的属性, 指向一个对象, 这样双方就可以通过这个对象下的属性或者方法进行信息交换了. 但需要补充的是, 不要企图传document这类的对象, 只能传传自己的(非严格界定, 但是大体意思大家应该懂). 官网上也提醒道, 如果要在iframe页面加载过程中让其中的脚本访问parentSandboxBridge, 则要在iframe中的脚本执行前就给iframe绑上parentSandboxBridge. 这时可以使用 “dominitialize” 事件.

这样一来, 跨域交互看起来终于是可行的了. 但是… 我做的是一个采集程序, 对方如果不调用我的bridge, 即使我搭好了又有什么意义呢?

后来才明白前面提到的sandboxRoot和documentRoot的意义. 对于现在多数的网页, 里面一般都会有脚本, 但那天看Google Adwords Keyword Tool的时候差点就伤心了, 从上面往下, 眼看着全是内嵌的脚本… 终于… 发现一个cues.js文件, 可能是GWT相关的信息初始化文件, 其URL是: https://adwords.google.com/cues/cues.js 于是, 将sandboxRoot设置为https://adwords.google.com/cues/, documentRoot设置为app-storage:/, 再把经过更改的cues.js文件放到app-storage:/, 这样一来, 当网页加载完成, 我安排在cues.js里的 “木马” 也就可以执行他的任务了~

如果要替换的JS文件是静态的, 那完全也可以更改后放到app:/目录(只读, 可以用trick写入, 但有兼容问题), 我放到app-storage:/是因为cues.js是动态生成的, 悲催吧…

虽然不能说是一个完美的解决方案, 但至少解决了自己的问题. 不过, 要是真遇上一个页面里没有脚本或者全是内嵌脚本的时候… 就伤了. 有人会问为什么要用那么低级的方式, 不直接发HTTP请求来解决? 老实说我也觉得蛮低级. 但Google Adwords Keyword Tool是用GWT开发的, 里面用来交换数据的格式并非常见的application/x-www-form-urlencoded, 而是text/gwt-rpc, 因为时间紧, 不能保证这是能轻松破解的(同样, 资料极少), 于是退而求其次了.

Adobe AIR写程序目录(app:/)的兼容性问题

最近接了个Adobe AIR的采集项目, 遇到不少问题, 当然也解决了不少问题. 这篇是几篇经验的第一篇.

网上应该可以很容易的搜到一个解决方案, 将路径转换为nativePath:

var file = new air.File("app:/some-file");
file = new air.File(file.nativePath.toString());

经过这样一个转换, 在Windows下似乎就没有问题了, 但是程序到了Mac上还是会报错. 这就比较伤心了…

我想写这个目录主要是因为想存储一些程序设置文件, 还有一些必要的东西. 继续搜索, 发现有人提出使用用户目录来保存. 当然, 当我最后知道答案的时候非常想不通为什么还有那么多人跟我一样笨…

除了app:/这个目录之外, 还有一个目录是app-storage:/, 一开始我以为这两个目录是等价的, 没有意识到差别. 后来才清楚… 所以用后者就可以了… 但是如果你是想更改程序文件, 就没办法了, 除非不考虑兼容, 或者直接把程序文件写在app-storage里. 囧.