使用 Local 构建用户可扩展的 Web 应用

在 2012 年与 Andrew Binstock 的一次采访中,Alan Kay 将浏览器描述为“一个笑话”。如果这让你感到惊讶,你会很高兴知道 Binstock 先生也感到惊讶。

Kay 指出的问题的一部分是众所周知的:功能集。浏览器今天正在做的是文字处理器和演示文稿工具几十年来一直在做的事情。但这似乎并不是最困扰他的问题。真正的问题?浏览器制造商认为他们正在制作一个应用程序,而实际上他们正在构建一个操作系统。

浏览器标签是一个非常小的环境。由于同源策略,应用程序的世界仅限于其主机公开的内容。不幸的是,远程主机通常是封闭的网络,用户无法控制它们。这阻止我们进行组合(浏览器中没有管道)和配置(无法为前端交换后端)。您可以更改选项卡,但不能将它们组合在一起。

由 IRON 构建

尽管存在这些问题,但 Web 仍然取得了成功,其原因是特定的。 在 2011 年发表的一篇论文中,微软、UT 和宾夕法尼亚州的研究人员概述了必要的品质(PDF):隔离、丰富、按需和联网。这些品质是为什么总的来说,您可以在 Web 上四处点击并做有趣的事情,而不用担心病毒会感染您的计算机。正如他们指出的那样,如果我们想改进 Web,我们必须小心不要使其变得软弱。

该研究团队提出了一种功能较少的核心浏览器,它可以随着页面下载其高级功能。他们的方法可以提高 Web 的丰富性和安全性,但首先需要进行“彻底的重构”。由于需要一些更直接的东西,我开发了 Local,这是一种与 HTML5 API 兼容的浏览器内程序架构。

通过 Web Workers 使用 HTTP

Local 使用 Web Workers 来运行其应用程序。它们是唯一可用的合适选择,因为 iframe 和对象能力工具(如 Google 的 Caja 或 Crockford 的 ADsafe)共享文档的线程。但是,Worker 无法访问文档,这使得它们难以使用。Local 对此的解决方案是将 Worker 视为 Web 主机,并通过 postMessage API 分派请求。Worker 反过来使用 HTML 响应,文档会呈现该 HTML。

这使得文档需要做出许多决策:流量权限、HTML 行为、加载哪些应用程序等等。这些决策构成了页面的“环境”,并且它们共同将应用程序组织成主机驱动的站点、可插入的 Web 应用或用户驱动的桌面环境。

Local 的基本要求之一是组合。互联网的优势——分布式互连——应该反映在其软件中。REST 是 Local 架构的统一接口,其理念借鉴自 Plan9 文件系统。在 HTML5 + Local 中,URI 可以表示远程服务端点、本地服务端点和编码的数据块。用于定位 javascript 的协议 (httpl://) 允许客户端区域链接到 Worker 并定位它们,而无需事件绑定。

这使 HTML 保持声明性:没有特定于应用程序的设置。环境可以引入其他接口原语。 Grimwire.com 尝试了 Web 意图的另一种方法,它会生成一个基于拖放的 UX。对于程序化组合,Local 依赖于 Link 标头,并提供“navigator”原型以以超媒体友好方式遵循这些链接。

安全性也是 Local 的基本要求。Web Worker 为不受信任的代码提供了安全的沙箱(来源 (PDF)来源)。内容安全策略允许环境限制内联脚本、样式和嵌入(包括图像)。然后,Local 为环境提供一个流量调度包装器以检查、清理、路由或拒绝应用程序请求。这使得设置策略(例如“仅限本地请求”)以及拦截 Cookie、Auth 和其他会话标头成为可能。这些策略的灵活性因环境而异。

示例环境:Markdown 查看器

为了了解其工作原理,让我们快速浏览一个简单的环境。这些代码段来自 blog.grimwire.com。页面 HTML、JS 和 Markdown 是静态提供的。一个名为“markdown.js”的 Worker 应用程序将其请求代理到托管的博客文章,并将它们的内容转换为 HTML。然后,环境将该 HTML 呈现到“内容”客户端区域,该区域由 Local 分割成其自己的浏览上下文(如 iframe)。

index.js

我们将查看的第一个文件是“index.js”,它是设置环境的脚本

// The Traffic Mediator
// examines and routes all traffic in the application
// (in our simple blog, we'll permit all requests and log the errors)
Environment.setDispatchWrapper(function(request, origin, dispatch) {
    var response = dispatch(request);
    // dispatch() responds with a promise which is
    //   fulfilled on 2xx/3xx and rejected on 4xx/5xx
    response.except(console.log.bind(console));
    return response;
});

// The Region Post-processor
// called after a response is rendered
// (gives the environment a chance to add plugins or styles to new content)
Environment.setRegionPostProcessor(function(renderTargetEl) {
    Prism.highlightAll(); // add syntax highlighting with prismjs
                          // (https://prism.npmjs.net.cn/)
});

// Application Load
// start a worker and configure it to load our "markdown.js" file
Environment.addServer('markdown.util', new Environment.WorkerServer({
    scriptUrl:'/local/apps/util/markdown.js',
    // ^^ this tells WorkerServer what app to load
    baseUrl:'/posts'
    // ^^ this tells markdown.js where to find the markdown files
}));

// Client Regions
// creates browsing regions within the page and populates them with content
var contentRegion = Environment.addClientRegion('content');
contentRegion.dispatchRequest('httpl://markdown.util/frontpage.md');

此处的环境非常简单。它使用了两个钩子:调度包装器和区域后处理器。更高级的环境可能会对 ClientRegionWorkerServer 原型进行子类型化,但这两个钩子应该可以自行提供很多控制。调度包装器主要用于安全和调试,而区域后处理器用于在新内容进入页面后添加 UI 行为或样式。

定义钩子后,环境将加载 markdown 代理并从内容区域分派一个请求以加载“frontpage.md”。Worker 异步加载,但 WorkerServer 会缓冲加载期间发出的请求,因此内容区域不必等待才能分派其请求。

当在 ClientRegion 中单击链接或提交表单时,Local 会将该事件转换为自定义“request”DOM 事件并从区域的元素中触发它。Local 的另一部分侦听“request”事件并处理分派和渲染过程。我们使用 dispatchRequest() 以编程方式在开始时触发我们自己的“request”事件。之后,Markdown 文件可以链接到“httpl://markdown.util/:post_name.md”,并且该区域将自行工作。

markdown.js

让我们快速看一下“markdown.js”

// Load Dependencies
// (these calls are synchronous)
importScripts('linkjs-ext/responder.js');
importScripts('vendor/marked.js'); // https://github.com/chjj/marked

// Configure Marked.js
marked.setOptions({ gfm: true, tables: true });

// Pipe Functions
// used with `Link.Responder.pipe()` to convert the response markdown to html
function headerRewrite(headers) {
    headers['content-type'] = 'text/html';
    return headers;
}
function bodyRewrite(md) { return (md) ? marked(md) : ''; }

// WorkerServer Request Handler
app.onHttpRequest(function(request, response) {
    // request the markdown file
    var mdRequest = Link.dispatch({
        method  : 'get',
        url     : app.config.baseUrl + request.path,
                            // ^^ the `baseUrl` given to us by index.js
        headers : { accept:'text/plain' }
    });
    // use helper libraries to pipe and convert the response back
    Link.responder(response).pipe(mdRequest, headerRewrite, bodyRewrite);
});

// Inform the environment that we're ready to handle requests
app.postMessage('loaded');

此脚本包含 Worker 应用程序所需的所有部分。至少,应用程序必须定义一个 HTTP 请求处理程序并将“loaded”消息发布回环境。(postMessage()MyHouse 的一部分,MyHouse 是 HTTPL 基于其构建的低级 Worker 管理器。)

在加载应用程序之前,Local 会将任何可能允许数据泄漏的 API(例如 XMLHttpRequest)设为空。当 Worker 使用 Link.dispatch 时,消息将传输到文档并传递给调度包装器。这就是安全策略如何执行的方式。Local 还使用 WorkerServer 构造函数中给定的值填充 app.config 对象,允许环境将配置传递给实例。

通过这两个代码段,我们已经了解了 Local 的基本工作原理。如果我们想要创建更高级的站点或桌面环境,我们将继续为客户端区域创建布局管理器、用于加载和控制 Worker 的 UI、用于执行权限的安全策略等等。

您可以在 github.com/pfraze/local-blog 上找到博客的完整源代码。

用户驱动的软件

Local 的目标是让用户驱动 Web 的开发。在其理想的未来,可以配置私有数据以保存到私有主机,点对点流量可以在使用 WebRTC 的浏览器内服务器之间未经记录地传输,可以即时混合使用 API,用户可以选择接口。我不想看到固定的网站,而是希望看到主机提供围绕不同任务(博客、银行、购物、开发等)构建的平台,并在其用户应用程序的服务上展开竞争。然后,像 Mint.com 这样的服务就可以停止索取您的银行凭据。相反,他们只需托管一个 JS 文件。

您可以通过阅读其 文档博客,以及尝试 Grimwire(一个处于早期阶段的通用部署)来开始使用 Local。源代码可以在 GitHub 上找到,并根据 MIT 许可证发布。

关于 Paul Frazee

Paul Frazee 是一位总部位于奥斯汀的 Web 开发人员,他对社交计算、虚拟现实和系统架构感兴趣。他目前维护着 Grimwire.com。

更多 Paul Frazee 的文章……

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道师和编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作——在瑞典和纽约市。他还经常在 http://robertnyman.com 上发表博客,并且喜欢旅行和结识新朋友。

更多 Robert Nyman [荣誉编辑] 的文章……


4 条评论

  1. aby

    您好,
    您能否告诉我从哪里开始学习 Firefox 编程?关于从哪种语言开始学习有什么建议吗?需要您的建议。我精通 Shell 脚本、Perl 和 Python,我也可以使用 C++

    此致,

    Aby
    http://www.oneshot.in
    http://www.privateren.com

    2013 年 3 月 18 日 05:14

    1. Robert Nyman [编辑]

      我建议阅读 参与 Mozilla 代码库

      2013 年 3 月 19 日 01:09

  2. John Thomas

    我使用 Web 应用作为普通应用的问题在于,您最终会在相对简单的应用下方获得不断增长的 Web 运行时。真正酷的事情是有一种方法可以获取 Web 应用并将其转换为 exe,从而剥离所有未使用的 Web 运行时方面。只是一个微弱的愿望。

    2013 年 3 月 22 日 11:42

  3. Macio

    本文为进一步的工作和对 Web 应用为何真实以及当前增长和推动 Web 应用整体增长和采用的力量的原因进行了良好的动机分析。

    我理解 IRON 中的观点作为支持,但我怀疑它们是“使 Web 应用具有吸引力的因素”。也许我们可以为此主题添加一个挑战——什么使 Web 应用具有吸引力?

    2013 年 3 月 26 日 06:38

本文的评论已关闭。