Start and call Ruby HTTP server in the same script

落爺英雄遲暮 提交于 2019-11-30 20:46:43

问题


I wonder how I could start a Ruby Rack application (such as Sinatra) and call it with Net::HTTP or similar in the same script. Of couse I could do something like...

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!'

...but it doesn't feel optimal to sleep for two seconds, waiting for Thin to start. What I need is some kind of callback when the server has started or does anybody have any other suggestions?


回答1:


If you look at the run! method in the sinatra source in base.rb you will see this:

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

There is no way to attach callbacks around here. BUT! as you see the :running setting is changed once the server is up.

So, the simple solution seems to be to have a thread watch App.settings.running in a small polling loop (every 500ms or something along those lines). Once running is true you can safely do your stuff.


Edit: improved version, with a bit of monkey patching goodness.
Adding an after_running callback to Sinatra:

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 takes a block that is called when the app is started.

Using that callback you can do this:

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')

This seems to be reliable for WEBrick, but when using Thin the callback is still sometimes called a little bit before the server accepts connections.




回答3:


I would use a semaphore (cf. Ruby Semaphores?) with a capacity of 1 for this task:

Main thread:

  1. Acquire the semaphore
  2. Spawn new thread
  3. Acquire semaphore (will block until released by the spawned thread)

Spawned web server thread:

  1. App.run!
  2. Release semaphore


来源:https://stackoverflow.com/questions/6557079/start-and-call-ruby-http-server-in-the-same-script

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!