【问题标题】:Start and call Ruby HTTP server in the same script在同一脚本中启动和调用 Ruby HTTP 服务器
【发布时间】:2026-01-02 00:00:02
【问题描述】:

我想知道如何启动一个 Ruby Rack 应用程序(例如 Sinatra)并在同一脚本中使用 Net::HTTP 或类似方法调用它。当然,我可以做类似...

require 'sinatra/base'
require 'net/http'

t = Thread.new do
    class App < Sinatra::Base
        get '/' do
            'Hi!'
        end
    end

    App.run! :host => 'localhost', :port => 1234
end

sleep 2

puts Net::HTTP.start('localhost', 1234) { |http| http.get('/') }.body

t.join

puts 'Bye!'

...但是睡两秒钟,等待 Thin 开始,感觉不是最佳选择。我需要的是服务器启动时的某种回调或有人有其他建议吗?

【问题讨论】:

标签: ruby multithreading events sinatra rack


【解决方案1】:

如果您查看 base.rb 中 sinatra 源代码中的 run! 方法,您会看到:

def run!(options={})
  ...
  handler.run self, :Host => bind, :Port => port do |server|
    [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
    set :running, true
  end
  ...
end

这里没有办法附加回调。但!如您所见,一旦服务器启动,:running 设置就会更改。

因此,简单的解决方案似乎是在一个小的轮询循环中(每 500 毫秒或类似的时间)有一个线程监视 App.settings.running。一旦running 为真,你就可以安全地做你的事情了。


编辑:改进版,带有一点猴子补丁的优点。
向 Sinatra 添加 after_running 回调:

class Sinatra::Base
  # Redefine the 'running' setting to support a threaded callback
  def self.running=(isup)
    metadef(:running, &Proc.new{isup})

    return if !defined?(after_running)
    return if !isup

    Thread.new do
      Thread.pass
      after_running
    end
  end
end

class App < Sinatra::Base

  set :after_running, lambda {
    puts "We're up!"
    puts Net::HTTP.start('localhost', 1234) { |http| http.get('/') }.body
    puts "Done"
  }

  get '/' do
    'Hi!'
  end

end

App.run! :host => "localhost", :port => 1234

【讨论】:

    【解决方案2】:

    run! in current Sinatra versions 接受一个在应用启动时调用的块。

    使用该回调您可以做到这一点:

    require 'thread'
    
    def sinatra_run_wait(app, opts)
      queue = Queue.new
      thread = Thread.new do
        Thread.abort_on_exception = true
        app.run!(opts) do |server|
          queue.push("started")
        end
      end
      queue.pop # blocks until the run! callback runs
    end
    
    sinatra_run_wait(TestApp, :port => 3000, :server => 'webrick')
    

    这对于 WEBrick 来说似乎是可靠的,但是当使用 Thin 时,有时仍然会在服务器接受连接之前调用回调。

    【讨论】:

    【解决方案3】:

    我会为此任务使用容量为 1 的信号量(参见Ruby Semaphores?):

    主线程:

    1. 获取信号量
    2. 产生新线程
    3. 获取信号量(将阻塞直到被生成的线程释放)

    衍生的网络服务器线程:

    1. App.run!
    2. 释放信号量

    【讨论】:

    • 这并不能解决任何问题,因为run! 会阻塞。信号量永远不会在生成的服务器线程上释放。