javascript - 通过HTTP从浏览器向服务器流式传输数据的方法

标签 javascript http browser xmlhttprequest

是否有类似 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);
});

我目前有一个网络套接字解决方案,但更喜欢常规 HTTP 以便与一些现有的服务器端代码更好地互操作。

编辑:我可以使用最新的浏览器 API。我正在查看 Fetch API,因为它支持 ArrayBuffers、DataViews、Files 等请求主体。如果我能以某种方式伪造出这些对象之一,以便我可以将 Fetch API 用于动态数据,那将对我有用。我尝试创建一个 Proxy 对象以查看是否调用了任何我可以修补的方法。不幸的是,浏览器(至少在 Chrome 中)似乎正在读取 native 代码而不是 JS 土地。但是,如果我在这方面有误,请纠正我。

最佳答案

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

Chrome 应用程序可以调用 chrome.sockets.tcp API,您可以在其上实现任何您想要的协议(protocol),包括 HTTP 和 HTTPS。这将为实现流式传输提供灵 active 。

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

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

list .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 发出硬编码的 PUT 请求(httpbin 是一个有用的测试站点,但请注意 does not support chunked encoding)。 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"
}

关于javascript - 通过HTTP从浏览器向服务器流式传输数据的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35899536/

相关文章:

javascript - 使用 ClojureScript 中的 ReactBootstrap 组件

c++ - 升压asio : Unable to read URL Body (JSON)

java - 读取二进制流直到遇到 "\r\n"

CSS 导航在不同浏览器中的变化

javascript - 构造函数中的 bind(this) 在 ReactJS 中做了什么

javascript - 在 jQuery 中创建一个新的(永久的)CSS 样式

javascript - Socket.io 错误挂接到 express.js

java - 我可以在谷歌应用引擎中使用 org.apache.http.client.HttpClient 吗?

cookies - asp.net core session 不工作,在响应头中设置cookie但未在浏览器中设置

java - 使 java applet 符合高安全标准