【问题标题】:Read request body without deleting it读取请求正文而不删除它
【发布时间】:2017-07-31 11:21:54
【问题描述】:

所以我有一个 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 读取后,服务器收到一个空的正文。如何在不修改原始上下文的情况下复制正文?

【问题讨论】:

    标签: crystal-lang


    【解决方案1】:

    Crystal 支持流式请求主体,这意味着一旦您在请求中流式传输,IO 为 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) 中运行所需的转换。这个方法比较复杂,之前的方法几乎涵盖了所有情况,所以我就不提供这个选项的代码示例了。

    【讨论】:

    • 未定义方法#reset,你的意思是#rewind?必须在 IO.copy 之后添加另一个 body_io.rewind 才能使其正常工作。谢谢! P.S:请为未来的用户更新您的答案;)
    • @VladFaust 感谢您发现这个错字!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-24
    • 1970-01-01
    • 2020-01-25
    • 1970-01-01
    • 2016-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多