Ruby,Puma和Sinatra:显示流输出

问题描述

@H_502_0@我有一个Puma应用程序,当您输入IP地址并通过单击复选框选择对其进行跟踪路由时,它将执行traceroute:

@H_502_0@下面的代码(实际上,该应用程序有更多功能,但出于这个问题,我将其简化了):

@H_502_0@ Site.erb:

class Pumatra < Sinatra::Base

    get '/' do
        erb :index
    end

    post '/run' do
    params.to_s
    end

 get'/traceroute_results' do
        @ipaddress  = params[:ipaddress]
        @traceroute = params[:traceroute]

            if @traceroute == "on"
                    stdout,status = Open3.capture2("traceroute -4 -w3 #{@ipaddress}")
                    @traceroute_result ="<pre><code>" + stdout + "</code></pre>"
            end

       @traceroute_header
            @traceroute_result
        end

        @traceroute_thread = Thread.new{traceroute()}
        @traceroute_thread.join

        erb :traceroute_results
    end
end
@H_502_0@我的views / index.erb文件包含此内容(仅向大家显示相关位):

....
        <div class="checkBox">
            <label><input type="checkBox" id="traceroute" 
             name="traceroute">Traceroute</label>
        </div>
....

        <!-- Start Results block -->
        <section id="results_block" style="display:none;" class="l_panel bg_color_white l_relative">
            <div id="traceroute_results" style="display:none;" class="l_grid"></div>
        </section>
        <!-- End Results block -->
@H_502_0@一切正常。 Traceroute运行,完成后将显示结果。 但是,有时这会花一些时间才能完成,因为跳数没有响应,尽管这是正常现象,但最终确实会给您带来结果。 我的问题是:仅出于装饰性原因,我想在运行时显示输出traceroute。因此,更多的是渐进式视图,而不是最后的完整输出。 这可能吗,我将如何去做?

@H_502_0@欣赏提示, J

@H_502_0@ 编辑:

@H_502_0@我在site.erb中尝试过

   get'/traceroute_results' do
                @traceroute_header = "<br>TCP Traceroute results:"
                  IO.popen("traceroute -4 -w3 #{@ipaddress}") do |io|
                        io.each do |line|
                        @traceroute_result = line
                        erb :traceroute_results
                        end
                  end

    end

@H_502_0@但是现在我的应用程序未显示traceroute的任何输出:(

@H_502_0@我的traceroute_results.erb文件

<%= @traceroute_header %>
<%= @traceroute_result %>
@H_502_0@ 编辑: Amadan的有用文章将我引到了我的site.erb中的这段代码

 get'/traceroute_results' do
        @ipaddress  = params[:ipaddress]
        @traceroute = params[:traceroute]

          if @traceroute == "on"
            ipaddress  = params[:ipaddress]
              stream do |res|
                res << erb(:traceroute_header)
                Open3.popen3("traceroute -T -4 #{@ipaddress}") do |stdin,stdout,stderr,wait_thr|
                  while line = stderr.gets
                    res << erb(:traceroute_results,{},{ out: line })
                  end
                end
              end
          end
  end
@H_502_0@并将我的traceroute_results文件更改为:

<%= out %>
@H_502_0@不幸的是,URL加载后我仍然什么也没收到。我可以告诉它执行traceroute的原因,因为返回结果需要一段时间,但是返回为空。

@H_502_0@真的很感谢任何人的帮助和耐心。 我正在用Sinatra运行Ruby Puma。任何人都可以在这样的设置中成功运行吗?我无法在Sinama中找到在puma上下文中实际可用的示例...:(

@H_502_0@谢谢, J

解决方法

首先,请注意erb不会发送响应;它通常成为响应,因为其结果是Ruby函数中的最后一件事,该函数隐式返回,并且Sinatra以get块的返回值作为响应。您当前的实现呈现您的子模板,但丢弃每个结果;同时,IO.popen(...) {...}返回nil,这是没有响应的原因。

现在,为了在Sinatra中生成流式响应,您可以返回响应#each的内容,或使用stream helper为您创建这样的对象。尝试更改为以下内容:

get '/traceroute_results' do
  ipaddress  = params[:ipaddress]
  stream do |res|
    res << erb(:traceroute_header)
    IO.popen("traceroute -4 -w3 #{ipaddress}") do |io|
      io.each do |line|
        res << erb(:traceroute_result,{},{ line: line })
      end
    end
  end
end

__END__
@@ traceroute_result
<div><%= line %></div>

@@ traceroute_header
<h4>Traceroute results</h4>
,

Amadan's answer既正确又不完整,在使用each时可能导致服务器上的线程不足。

我不是Sinatra开发人员,但是我编写服务器代码,因此我不得不警告不要使用each

each的问题是服务器在等待each进行迭代并返回时,服务器对该线程没有控制权-这会阻止线程处理其他请求

更好的解决方案是使用线程池和队列来调用traceroute,并使用SSE / WebSockets连接将输出从队列流传输到用户。

这可以保护服务器免受DoS攻击,并允许它:

  1. 如果同时有太多请求到达,则显示“等待中”的消息(也许以队列的长度为准)。

  2. traceroute到客户端的情况下将数据流传输到客户端,而不会阻塞服务器(将数据推送到客户端)。

  3. 如果客户端断开连接,则取消traceroute

这可以使用WebSocket本地解决方案或劫持解决方案来执行。