读取请求主体,而不删除它

问题描述:

所以我有必须知道什么是请求体内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支持流式请求体,这意味着一旦流入请求,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)内运行所需的转换。这种方法相当复杂,前面的方法几乎覆盖了所有情况,所以我不会提供此选项的代码示例。

+1

未定义的方法'#重置',你的意思是'#rewind'?必须在'IO.copy'之后添加另一个'body_io.rewind'才能使其工作。谢谢! P.S:请更新您对未来用户的回答;) –

+0

@VladFaust谢谢您收到该错误! – RX14