读取请求正文而不删除它

时间:2017-07-31 11:21:54

标签: crystal-lang

所以我有一个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 个答案:

答案 0 :(得分:8)

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并在{{1}内运行所需的转换}}。这种方法非常复杂,以前的方法几乎涵盖了所有情况,因此我不会为此选项提供代码示例。