在Rails中将大型PUT或POST请求正文读取为流,而无需缓冲到磁盘

问题描述

我正在用Rails 6进行API用例的实验。用户会发布大型请求,这些请求将由Rails接收并流到另一个地方。这可能会与JRuby一起运行,但是现在我正在使用MRI Ruby进行测试。 Web服务器是Puma(开箱即用的导轨安装)。

在上载请求流时,是否有任何方法可以读取请求主体,或者Rails是否需要等待整个请求上载才能访问它?

使用此Curl命令:

curl  -i -X PUT -T data.bin "http://localhost:3000/welcome/stream"

这个简单的控制器:

  def stream
    puts "The stream was called"

    io = request.body
    until io.eof?
      io.read(1024*1024)
      puts "Read 1MB"
    end

    head 200,content_type: "text/html"
  end

似乎整个请求都在Rails对其进行访问之前在某个地方上演,因为我的任何put出现在控制台上都需要很长时间。

稍作搜索,似乎是Web服务器(Puma,Unicorn等)缓冲了数据,并且仅在接收到所有数据后才调用Rails。

无论如何,有没有开始读取流的内容,或者避免对其进行缓冲?

是Rack还是Puma进行缓冲?如果是这样,它将在哪里缓冲数据?

解决方法

是Rack还是Puma进行缓冲?如果是这样,它将在哪里缓冲数据?

服务器会收集整个流,然后再将其传递给Rack / Rails ...,这样您就无法在主体完全上传之前开始处理请求。

进行此设计的理由很充分,因为服务器将使用可用线程来轮询IO而不是进行阻塞,从而使您的应用程序可以在后台执行IO操作的同时处理其他请求。

否则,当您的应用程序在不完整的流上调用read时,IO将阻塞线程(并扩展为服务器),从而导致并发性失败。

无论如何,有没有开始读取流的内容,或者避免对其进行缓冲?

您可以实现自己的服务器(不建议使用),也可以使用AJAX / WebSockets(更常见的方法)上传点点滴滴。