读取请求体而不删除它

3

我有一个MyHandler,需要知道请求体中的内容:

class MyHandler
  include HTTP::Handler

  def call(context)
    p "MyHandler got body: " + context.request.body.not_nil!.gets_to_end
    call_next(context)
  end
end

server = HTTP::Server.new(42, [MyHandler.new]) do |context|
  p "Server got body: " + context.request.body.not_nil!.gets_to_end
end

正如预期的那样,MyHandler 读取后,服务器接收到一个空的请求体。如何在不修改原始上下文的情况下复制该请求体?

1个回答

9
Crystal支持流式请求体,这意味着一旦你流式传入请求,I/O操作就到达了EOF,第二个处理程序将无法读取任何数据。

解决这个问题的一个简单方法是使用body_string = context.request.body.try(&.gets_to_end)检索整个内容,然后使用context.request.body = body_string设置请求正文为返回的字符串。 这将整个正文缓冲到内存中,然后将正文设置为存储在内存中的缓冲。这种方法的缺点是攻击者可以发送一个大小无限的请求正文并占用服务器上的所有内存,导致DOS攻击。另一个缺点是如果你正在处理二进制数据,那么你需要使用#to_slice将字符串转换成片来处理它。

如果你有一个最大正文大小的想法,解决DOS攻击问题的一种方法是:如果正文太大,则拒绝请求:

if body = context.request.body
  body_io = IO::Memory.new
  bytes_read = IO.copy(body, body_io, limit: 1_048_576) # 1GiB limit
  body_io.rewind
  if bytes_read == 1_048_576
    # Fail request
  end

  # use body_io

  body_io.rewind # Reset body_io to start
  context.request.body = body_io
end

如果您需要接受一个大小无限的主体,并且不将其缓冲到内存中,则应创建自定义IO实现,该实现包装现有的主体IO并在IO#read(Bytes)内运行所需的转换。这种方法非常复杂,而前面的方法几乎涵盖了所有情况,因此我不会为此选项提供代码示例。

1
未定义方法#reset,你指的是 #rewind 吗?不得不在 IO.copy 之后再添加另一个 body_io.rewind 才能使它正常工作。谢谢!附言:请为将来的用户更新你的答案 ;) - Vlad Faust
@VladFaust 感谢您发现了那个错别字! - Stephie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接