我不知道如何使用纯 HTML5 API 做到这一点,但一种可能的解决方法是使用 Chrome 应用程序作为后台服务为网页提供附加功能。如果您已经愿意使用开发浏览器并启用实验性功能,那么这似乎只是比这更进一步的一步。
Chrome 应用程序可以调用chrome.sockets.tcp
API,您可以在该 API 上实现您想要的任何协议,包括 HTTP 和 HTTPS。这将提供实现流式传输的灵活性。
常规网页可以使用chrome.runtime
API与应用程序交换消息,只要应用程序声明此用法即可。这将允许您的网页对您的应用程序进行异步调用。
我写了这个简单的应用程序作为概念证明:
清单文件.json
{
"manifest_version" : 2,
"name" : "Streaming Upload Test",
"version" : "0.1",
"app": {
"background": {
"scripts": ["background.js"]
}
},
"externally_connectable": {
"matches": ["*://localhost/*"]
},
"sockets": {
"tcp": {
"connect": "*:*"
}
},
"permissions": [
]
}
背景.js
var mapSocketToPort = {};
chrome.sockets.tcp.onReceive.addListener(function(info) {
var port = mapSocketToPort[info.socketId];
port.postMessage(new TextDecoder('utf-8').decode(info.data));
});
chrome.sockets.tcp.onReceiveError.addListener(function(info) {
chrome.sockets.tcp.close(info.socketId);
var port = mapSocketToPort[info.socketId];
port.postMessage();
port.disconnect();
delete mapSocketToPort[info.socketId];
});
// Promisify socket API for easier operation sequencing.
// TODO: Check for error and reject.
function socketCreate() {
return new Promise(function(resolve, reject) {
chrome.sockets.tcp.create({ persistent: true }, resolve);
});
}
function socketConnect(s, host, port) {
return new Promise(function(resolve, reject) {
chrome.sockets.tcp.connect(s, host, port, resolve);
});
}
function socketSend(s, data) {
return new Promise(function(resolve, reject) {
chrome.sockets.tcp.send(s, data, resolve);
});
}
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
if (!port.state) {
port.state = msg;
port.chain = socketCreate().then(function(info) {
port.socket = info.socketId;
mapSocketToPort[port.socket] = port;
return socketConnect(port.socket, 'httpbin.org', 80);
}).then(function() {
// TODO: Layer TLS if needed.
}).then(function() {
// TODO: Build headers from the request.
// TODO: Use Transfer-Encoding: chunked.
var headers =
'PUT /put HTTP/1.0\r\n' +
'Host: httpbin.org\r\n' +
'Content-Length: 17\r\n' +
'\r\n';
return socketSend(port.socket, new TextEncoder('utf-8').encode(headers).buffer);
});
}
else {
if (msg) {
port.chain = port.chain.then(function() {
// TODO: Use chunked encoding.
return socketSend(port.socket, new TextEncoder('utf-8').encode(msg).buffer);
});
}
}
});
});
此应用程序没有用户界面。它侦听连接并向http://httpbin.org/put
(httpbin是一个有用的测试站点,但请注意它不支持分块编码)发出硬编码的 PUT 请求。PUT 数据(当前硬编码为 17 个八位字节)从客户端(根据需要使用尽可能少的消息)并发送到服务器。来自服务器的响应流回客户端。
这只是一个概念证明。一个真正的应用程序可能应该:
- 连接到任何主机和端口。
- 使用传输编码:分块。
- 表示流数据结束。
- 处理套接字错误。
- 支持 TLS(例如使用Forge)
这是一个示例网页,它使用应用程序即服务执行流式上传(17 个八位字节)(请注意,您必须配置自己的应用程序 ID):
<pre id="result"></pre>
<script>
var MY_CHROME_APP_ID = 'omlafihmmjpklmnlcfkghehxcomggohk';
function streamingUpload(url, options) {
// Open a connection to the Chrome App. The argument must be the
var port = chrome.runtime.connect(MY_CHROME_APP_ID);
port.onMessage.addListener(function(msg) {
if (msg)
document.getElementById("result").textContent += msg;
else
port.disconnect();
});
// Send arguments (must be JSON-serializable).
port.postMessage({
url: url,
options: options
});
// Return a function to call with body data.
return function(data) {
port.postMessage(data);
};
}
// Start an upload.
var f = streamingUpload('https://httpbin.org/put', { method: 'PUT' });
// Stream data a character at a time.
'how now brown cow'.split('').forEach(f);
</script>
当我在安装了应用程序的 Chrome 浏览器中加载此网页时,httpbin 返回:
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 19 Jun 2016 16:54:23 GMT
Content-Type: application/json
Content-Length: 240
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "how now brown cow",
"files": {},
"form": {},
"headers": {
"Content-Length": "17",
"Host": "httpbin.org"
},
"json": null,
"origin": "[redacted]",
"url": "http://httpbin.org/put"
}