去年年底,Mozilla 的员工们聚在一起进行了一周的合作和规划。在这周期间,一个小组组建起来,设想以更加 P2P 为中心的 Web,Firefox OS 的未来将会是什么样。特别是,我们一直在研究利用技术来共同实现离线 P2P 连接,例如 蓝牙、NFC 和 WiFi Direct.
由于这些技术只提供设备之间通信的方式,因此立即明确我们需要一个协议让应用程序发送和接收数据。我很快意识到我们已经拥有一个可以利用的标准协议,用于在 Web 应用程序中传输数据——HTTP。
通过使用 HTTP,我们已经拥有了应用程序在客户端发送和接收数据所需的一切,但我们仍然需要在浏览器中运行一个 Web 服务器来启用离线 P2P 通信。虽然这种类型的 HTTP 服务器功能最适合作为标准化 WebAPI 的一部分,以便将其嵌入到 Gecko 中,但实际上我们已经在 Firefox OS 中拥有了今天在 JavaScript 中实现这一切所需的一切!
navigator.mozTCPSocket
打包的应用程序 可以访问原始 TCP 和 UDP 网络套接字,但由于我们处理的是 HTTP,因此我们只需要使用 TCP 套接字。对 TCPSocket API 的访问是通过 navigator.mozTCPSocket 公开的,目前它只对具有 tcp-socket 权限的“特权”打包的应用程序公开。
"type": "privileged",
"permissions": {
"tcp-socket": {}
},
为了响应传入的 HTTP 请求,我们需要创建一个新的 TCPSocket,它在已知端口(例如 8080)上监听。
var socket = navigator.mozTCPSocket.listen(8080);
当收到传入的 HTTP 请求时,TCPSocket
需要通过onconnect
处理程序处理请求。onconnect
处理程序将接收一个用于服务请求的TCPSocket
对象。您收到的TCPSocket
将在每次收到额外的 HTTP 请求数据时调用其自己的ondata
处理程序。
socket.onconnect = function(connection) {
connection.ondata = function(evt) {
console.log(evt.data);
};
};
通常,HTTP 请求会导致ondata
处理程序被调用一次。但是,在 HTTP 请求有效负载非常大的情况下(例如文件上传),ondata
处理程序将在每次缓冲区被填充时触发,直到整个请求有效负载被传递。
为了响应 HTTP 请求,我们必须将数据发送到从onconnect
处理程序收到的TCPSocket
。
connection.ondata = function(evt) {
var response = 'HTTP/1.1 200 OK\r\n';
var body = 'Hello World!';
response += 'Content-Length: ' + body.length + '\r\n';
response += '\r\n';
response += body;
connection.send(response);
connection.close();
};
上面的示例发送了一个正确的 HTTP 响应,主体中包含“Hello World!”。有效的 HTTP 响应必须包含一个状态行,该状态行包含 HTTP 版本HTTP/1.1
、响应代码200
和响应原因OK
,后面是 CR+LF \r\n
字符序列。状态行紧随其后的是 HTTP 标头,每行一个,用 CR+LF 字符序列分隔。在标头之后,需要另一个 CR+LF 字符序列将标头与 HTTP 响应的主体分隔开。
FxOS Web 服务器
现在,我们很可能希望超越简单的静态“Hello World!”响应,去做一些事情,比如解析 URL 路径并从 HTTP 请求中提取参数,以便用动态内容进行响应。碰巧我之前已经实现了一个基本功能的 HTTP 服务器库,您可以将其包含在您自己的 Firefox OS 应用程序中!
FxOS Web 服务器 可以解析 HTTP 请求的所有部分,以获取各种内容类型,包括application/x-www-form-urlencoded
和multipart/form-data
。它还可以优雅地处理用于文件上传的大型 HTTP 请求,并可以发送用于提供诸如图像和视频等内容的大型二进制响应。您可以下载 GitHub 上的 FxOS Web 服务器的源代码,以便手动将其包含在您的项目中,或者您可以使用 Bower 获取最新版本。
bower install justindarc/fxos-web-server --save
下载源代码后,您需要使用<script>
标签或类似 RequireJS 的模块加载器将dist/fxos-web-server.js
包含到您的应用程序中。
简单的文件存储应用程序
接下来,我将向您展示如何使用 FxOS Web 服务器 来构建一个简单的 Firefox OS 应用程序,该应用程序可以让您像使用便携式闪存驱动器一样使用您的移动设备来存储和检索文件。您可以在 GitHub 上查看完成产品的源代码。
在我们深入代码之前,让我们设置我们的 应用程序清单,以获得访问 DeviceStorage 和 TCPSocket 的权限。
{
"version": "1.0.0",
"name": "WebDrive",
"description": "A Firefox OS app for storing files from a web browser",
"launch_path": "/index.html",
"icons": {
"128": "/icons/icon_128.png"
},
"type": "privileged",
"permissions": {
"device-storage:sdcard": { "access": "readwrite" },
"tcp-socket": {}
}
}
我们的应用程序不需要太多 UI,只需要在设备上的“WebDrive”文件夹中列出文件,因此我们的 HTML 会非常简单。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebDrive</title>
<meta name="description" content="A Firefox OS app for storing files from a web browser">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<script src="bower_components/fxos-web-server/dist/fxos-web-server.js"></script>
<script src="js/storage.js"></script>
<script src="js/app.js"></script>
</head>
<body>
<h1>WebDrive</h1>
<hr>
<h3>Files</h3>
<ul id="list"></ul>
</body>
</html>
如您所见,除了 app.js 之外,我还包含了 fxos-web-server.js。我还包含了一个名为 storage.js 的 DeviceStorage 帮助程序模块,因为枚举文件可能变得比较复杂。这将有助于我们专注于与手头任务相关的代码。
我们需要做的第一件事是创建HTTPServer
和Storage
对象的新实例。
var httpServer = new HTTPServer(8080);
var storage = new Storage('sdcard');
这将在端口 8080 上初始化一个新的HTTPServer
,并创建一个指向设备 SD 卡的新Storage
帮助程序实例。为了让我们的HTTPServer
实例发挥作用,我们必须监听并处理“request”事件。当收到传入的 HTTP 请求时,HTTPServer
将发出一个“request”事件,将解析后的 HTTP 请求作为HTTPRequest
对象传递给事件处理程序。
HTTPRequest
对象包含 HTTP 请求的各种属性,包括 HTTP 方法、路径、标头、查询参数和表单数据。除了请求数据之外,“request”事件处理程序还传递了一个HTTPResponse
对象。HTTPResponse
对象允许我们以文件或字符串的形式发送响应,并设置响应标头。
httpServer.addEventListener('request', function(evt) {
var request = evt.request;
var response = evt.response;
// Handle request here...
});
当用户请求我们 Web 服务器的根 URL 时,我们希望向他们显示设备上“WebDrive”文件夹中存储的文件列表,以及一个用于上传新文件的FileInput。为了方便起见,我们将创建两个帮助程序函数来生成我们要在 HTTP 响应中发送的 HTML 字符串。一个只是生成文件列表,我们将在本地重新使用它来显示设备上的文件,另一个将生成要在 HTTP 响应中发送的整个 HTML 文档。
function generateListing(callback) {
storage.list('WebDrive', function(directory) {
if (!directory || Object.keys(directory).length === 0) {
callback('<li>No files found</li>');
return;
}
var html = '';
for (var file in directory) {
html += `<li><a href="/${encodeURIComponent(file)}" target="_blank">${file}</a></li>`;
}
callback(html);
});
}
function generateHTML(callback) {
generateListing(function(listing) {
var html =
`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebDrive</title>
</head>
<body>
<h1>WebDrive</h1>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
<hr>
<h3>Files</h3>
<ul>${listing}</ul>
</body>
</html>`;
callback(html);
});
}
您会注意到,我们使用的是 ES6 模板字符串 来生成我们的 HTML。如果您不熟悉 模板字符串,它们允许我们拥有多行字符串,这些字符串会自动包含空格和换行符,而且我们可以执行基本字符串插值,它会自动插入${}
语法中的值。这对于生成 HTML 特别有用,因为它允许我们跨越多行,因此当嵌入到 JavaScript 代码中时,我们的模板标记保持高度可读性。
现在我们有了帮助程序函数,让我们在“request”事件处理程序中发送我们的 HTML 响应。
httpServer.addEventListener('request', function(evt) {
var request = evt.request;
var response = evt.response;
generateHTML(function(html) {
response.send(html);
});
});
截至目前,我们的“request”事件处理程序将始终使用设备上“WebDrive”文件夹中所有文件的 HTML 页面进行响应。但是,在我们能够接收任何请求之前,我们必须首先启动HTTPServer
。我们将在 DOM 准备好后执行此操作,并且在执行此操作的同时,我们还将在本地呈现文件列表。
window.addEventListener('DOMContentLoaded', function(evt) {
generateListing(function(listing) {
list.innerHTML = listing;
});
httpServer.start();
});
我们还应该确保在应用程序终止时停止HTTPServer
,否则网络套接字可能永远不会被释放。
window.addEventListener('beforeunload', function(evt) {
httpServer.stop();
});
此时,我们的 Web 服务器应该已经启动并运行!使用 WebIDE 在您的设备或模拟器上安装该应用程序。安装完成后,启动该应用程序,并将您的桌面浏览器指向您设备的 IP 地址和端口 8080(例如:http://10.0.1.12:8080)。
您应该会在您的桌面浏览器中看到我们的索引页面加载,但上传表单尚未连接,并且如果您在设备上的“WebDrive”文件夹中存在任何文件,则无法下载它们。让我们首先通过创建一个新的帮助程序函数来保存HTTPRequest
中接收到的文件来连接文件上传。
function saveFile(file, callback) {
var arrayBuffer = BinaryUtils.stringToArrayBuffer(file.value);
var blob = new Blob([arrayBuffer]);
storage.add(blob, 'WebDrive/' + file.metadata.filename, callback);
}
此函数将首先使用 fxos-web-server.js 附带的BinaryUtils
实用程序将文件的內容转换为 ArrayBuffer。然后,我们创建一个 Blob,将其传递给我们的Storage
帮助程序,以将其保存在 SD 卡上的“WebDrive”文件夹中。请注意,文件名可以从文件的metadata
对象中提取,因为它使用 ‘multipart/form-data’ 编码传递到服务器。
现在我们有了用于保存上传文件的帮助程序,让我们在“request”事件处理程序中将其连接起来。
httpServer.addEventListener('request', function(evt) {
var request = evt.request;
var response = evt.response;
if (request.method === 'POST' && request.body.file) {
saveFile(request.body.file, function() {
generateHTML(function(html) {
response.send(html);
});
generateListing(function(html) {
list.innerHTML = html;
});
});
return;
}
generateHTML(function(html) {
response.send(html);
});
});
现在,每当收到包含请求主体中“file”参数的 HTTPPOST
请求时,我们都会将该文件保存到 SD 卡上的“WebDrive”文件夹中,并使用更新的文件列表索引页面进行响应。同时,我们还将更新本地设备上的文件列表,以显示新添加的文件。
我们应用程序中唯一剩下的部分是连接下载文件的功能。再次,让我们更新“request”事件处理程序来执行此操作。
httpServer.addEventListener('request', function(evt) {
var request = evt.request;
var response = evt.response;
if (request.method === 'POST' && request.body.file) {
saveFile(request.body.file, function() {
generateHTML(function(html) {
response.send(html);
});
generateListing(function(html) {
list.innerHTML = html;
});
});
return;
}
var path = decodeURIComponent(request.path);
if (path !== '/') {
storage.get('WebDrive' + path, function(file) {
if (!file) {
response.send(null, 404);
return;
}
response.headers['Content-Type'] = file.type;
response.sendFile(file);
});
return;
}
generateHTML(function(html) {
response.send(html);
});
});
这一次,我们的“请求”事件处理程序将检查请求的路径,以查看是否请求了根目录以外的 URL。如果是,我们假设用户正在请求下载文件,然后我们使用我们的 `Storage` 助手获取该文件。如果找不到文件,我们将返回 HTTP 404 错误。否则,我们将响应头中的“Content-Type”设置为文件的 MIME 类型,并将文件与 `HTTPResponse` 对象一起发送。
您现在可以使用 WebIDE 将应用程序重新安装到您的设备或模拟器,然后再次将您的桌面浏览器指向您设备的 IP 地址(端口 8080)。现在,您应该能够使用您的桌面浏览器从您的设备上传和下载文件!
将 Web 服务器嵌入 Firefox OS 应用程序中所带来的潜在用例几乎是无限的。您不仅可以从您的设备向桌面浏览器提供 Web 内容,就像我们刚刚在此处所做的那样,而且您还可以从一台设备向另一台设备提供内容。这也意味着您可以使用 HTTP 在同一设备上的应用程序之间发送和接收数据!自从问世以来,FxOS Web Server 一直作为 Mozilla 的一些激动人心的实验的基础。
-
Guillaume Marty 将 FxOS Web Server 与他令人惊叹的 jsSMS Master System/Game Gear 模拟器相结合,以结合 WiFi Direct 在两台设备之间实现多人游戏。
-
Gaia 团队的几位成员已经使用 FxOS Web Server 和 dns-sd.js 创建了一个应用程序,允许用户通过 WiFi 发现并与朋友分享应用程序。
-
我个人已经使用 FxOS Web Server 构建了一个应用程序,它允许您使用 WiFi Direct 与附近的用户共享文件,而无需互联网连接。您可以在此处查看应用程序的实际操作
我期待着看到 FxOS Web Server 下一步将构建的所有令人兴奋的事情!
关于 Justin D'Arcangelo
Mozilla 的软件工程师,创建尖端的移动 Web 应用程序。自豪的父亲、丈夫、音乐家、黑胶唱片爱好者,以及无所不能的修补匠。
14 条评论