是否有任何类似XHR的浏览器API可用于通过HTTP将二进制流传输到服务器?

我想提出一个HTTP PUT请求并以编程方式随时间推移创建数据。我不想一次创建所有这些数据,因为可能有大量的数据坐在内存中。一些伪代码来说明我正在得到什么:

var dataGenerator = new DataGenerator(); // Generates 8KB UInt8Array every second
var streamToWriteTo;
http.put('/example', function (requestStream) {
  streamToWriteTo = requestStream;
});

dataGenerator.on('data', function (chunk) {
  if (!streamToWriteTo) {
    return;
  }
  streamToWriteTo.write(chunk);
});

我目前有一个Web套接字解决方案,但是我希望使用常规HTTP以便与某些现有服务器端代码更好地互操作。

编辑:我可以使用最新的浏览器API。我一直在查看Fetch API,因为它支持请求主体的ArrayBuffers,DataViews,Files等。如果我能以某种方式伪造这些对象之一,以便可以将Fetch API与动态数据一起使用,那将对我有用。我尝试创建一个Proxy对象,以查看是否有任何可以猴子补丁的方法被调用。不幸的是,似乎浏览器(至少在Chrome中)使用本机代码而不是JS语言进行读取。但是,如果我做错了,请纠正我。

最佳答案

我不知道如何使用纯HTML5 API来执行此操作,但是一种可能的解决方法是将Chrome应用程序用作后台服务,以向网页提供其他功能。如果您已经愿意使用开发浏览器并启用实验性功能,那么这似乎只是一个进一步的步骤。

Chrome应用程序可以调用 chrome.sockets.tcp API,您可以在该API上实现所需的任何协议(protocol),包括HTTP和HTTPS。这将提供实现流的灵活性。

常规网页可以使用 chrome.runtime API与App交换消息,只要App declares this usage即可。这将允许您的网页对应用程序进行异步调用。

我写了这个简单的应用程序作为概念证明:

manifest.json

{
  "manifest_version" : 2,

  "name" : "Streaming Upload Test",
  "version" : "0.1",

  "app": {
    "background": {
      "scripts": ["background.js"]
    }
  },

  "externally_connectable": {
    "matches": ["*://localhost/*"]
  },

  "sockets": {
    "tcp": {
      "connect": "*:*"
    }
  },

  "permissions": [
  ]
}

background.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发出硬编码的PUT请求(httpbin是有用的测试站点,但请注意does not support chunked encoding)。 PUT数据(当前硬编码为17个八位位组)从客户端流式传输(使用所需的消息数量少或多)并发送到服务器。服务器的响应将流回客户端。

这仅仅是概念的证明。一个真正的应用程序可能应该:
  • 连接到任何主机和端口。
  • 使用Transfer-Encoding:分块。
  • 指示流数据结束。
  • 处理套接字错误。
  • 支持TLS(例如with 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"
    }
    

    10-06 04:34
    查看更多