2017-07-31 33 views
1

所以我有必須知道什麼是請求體內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已經閱讀後,服務器收到一個空體。如何在不修改原始上下文的情況下複製正文?

回答

6

核心問題是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