Sinatra randomly starts demo server - ruby

I am writing a small proxy-checking utility, here is my code so far:
require "thread"
require "socket"
require "http"
require "sinatra"
host = "0.0.0.0"
port = 6660
Thread.new do
class App < Sinatra::Base
set :server, :thin
get '/' do
env.select {|k,v| k.start_with? 'HTTP_'}.collect {|pair| "#{pair[0].sub(/^HTTP_/, '')}: #{pair[1]}"}.join "\n"
end
end
App.run! host: host, port: port
end
sleep 2
queue = Queue.new
ext_ip = Socket.ip_address_list.detect{|intf| intf.ipv4?}.ip_address # and !intf.ipv4_loopback? and !intf.ipv4_multicast? and !intf.ipv4_private?}
url = "http://#{url}:#{port}/"
Thread.new do
queue << proxy.split(":") while proxy = gets.chomp
end
servers = [1, 2, 3, 4, 5].map {
Thread.new do
until queue.empty?
p "shit", queue.pop
headers = HTTP.via(*queue.pop).get(url).split("\n").map {|l| l.split(" ", 2)}.to_h
p headers
end
end
}.each do |t|
t.join
end
For some reason, this does not only start a sinatra server on localhost:6660 but also on localhost:4567, and I absolutely don't understand why.
This is the output I get:
== Sinatra (v1.4.6) has taken the stage on 6660 for development with backup from Thin
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on localhost:6660, CTRL+C to stop
Stopping ...
== Sinatra has ended his set (crowd applauds)
== Sinatra (v1.4.6) has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
Where does that second server come from?? When I open it in my browser it is the default Sinatra splash.

Rather than require "sinatra", use require "sinatra/base". Requiring just sinatra enables the built in server, which will start serving the (empty) Sinatra::Application app in an at_exit handler.

Related

How to programmatically get Sinatra's active port?

I'm creating a simple and portable web application with Sinatra on Ruby, and I'm letting the system find an open port for the server to use with the following:
require 'sinatra'
require 'socket'
socket = Socket.new(:INET, :STREAM, 0)
socket.bind(Addrinfo.tcp("127.0.0.1", 0))
port = socket.local_address.to_s.chomp("\x0").to_i
set :port, port
set :bind, "127.0.0.1"
get "/" do
"Hello, World!"
end
I'd like to launch the browser to view the application automatically, but the problem is that both the port variable and Sinatra's settings.port are set to 0, so I can't get the URL of the server.
The launch code comes after get "/" do block:
Thread.new do
system "chromium-browser " <<
"-app='http://127.0.0.1:#{port}' " <<
"--no-sandbox > /dev/null 2>&1"
end
After the system starts, I can see the port in the WEBrick output, but how can I get the port that the system assigns to the socket beforehand?
Try this
require 'sinatra'
require 'socket'
socket = Socket.new(:INET, :STREAM, 0)
socket.bind(Addrinfo.tcp("127.0.0.1", 0))
port = socket.local_address.ip_port
socket.close
set :port, port
set :bind, "127.0.0.1"
get "/" do
"Hello, World!"
end
socket.local_address.ip_port would give you the port info, but you need to close that socket before starting sinatra or it will fail with Errno::EADDRINUSE

How to run multiple Sinatra instances without anything else?

I want to spawn a lot of configurable Sinatra servers.
For example:
require 'sinatra/base'
class AwesomeOne
def initialize port
#sapp = Sinatra.new {
set :port => port
get '/'do
"Hi!"
end
}
end
def run!
#sapp.run!
end
end
and then:
ths = []
(1111..9999).each { |port|
ths.push Thread.new { AwesomeOne.new(port).run! }
}
But something goes wrong: i can't access each page. But some of them seems accessible.
So, how to run Sinatra multiple times in one .rb file?
I happen to need the same thing soon so I did some research.
You could use Sinatra's cascade, routing or middleware options, see https://www.safaribooksonline.com/library/view/sinatra-up-and/9781449306847/ch04.html and search for 'multiple', I advise you to buy the book and read it, it is all very usefull stuff !
But more leaning to your approach you can use the eventmachine Sinatra allready uses to run multiple apps on different ports, Ruby itself is started only once.
See http://recipes.sinatrarb.com/p/embed/event-machine for explanation and more examples. I combined the example with your code.
# adapted from http://recipes.sinatrarb.com/p/embed/event-machine
require 'eventmachine'
require 'sinatra/base'
require 'thin'
def run(opts)
EM.run do
server = opts[:server] || 'thin'
host = opts[:host] || '0.0.0.0'
port = opts[:port] || '8181'
web_app = opts[:app]
dispatch = Rack::Builder.app do
map '/' do
run web_app
end
end
unless ['thin', 'hatetepe', 'goliath'].include? server
raise "Need an EM webserver, but #{server} isn't"
end
Rack::Server.start({
app: dispatch,
server: server,
Host: host,
Port: port,
signals: false,
})
end
end
class HelloApp < Sinatra::Base
configure do
set :threaded, true
end
get '/hello' do
"Hello World from port #{request.port}"
end
end
ths = []
(4567..4569).each do |port|
ths.push Thread.new { run app: HelloApp.new, port: port }
end
ths.each{|t| t.join}
output
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on 0.0.0.0:4567, CTRL+C to stop
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on 0.0.0.0:4568, CTRL+C to stop
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on 0.0.0.0:4569, CTRL+C to stop
In windows cmd netstat -ab this gives
TCP 0.0.0.0:4567 ******:0 LISTENING
TCP 0.0.0.0:4568 ******:0 LISTENING
TCP 0.0.0.0:4569 ******:0 LISTENING
And the hello example on all ports works.

Connecting to background EventMachine application for unit testing

I am writing a headless Ruby application using EventMachine that communicates over sockets. I want to write some unit tests for this app. This means that my Ruby test script needs to launch that app in the background, perform socket communication with it, and then close that process.
This code fails. The socket connection is refused.
require 'socket'
PORT = 7331
CMD = File.expand_path("../../bin/rb3jay",__FILE__)
#thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
#socket = TCPSocket.open('localhost', PORT)
#=> Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 7331
If I inject a 2 second delay before attempting the socket connection, it works as desired:
#thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
sleep 2
#socket = TCPSocket.open('localhost', PORT)
This seems a gross hack. Maybe 2 seconds is long enough for my machine, but too short somewhere else.
How should I correctly launch my EventMachine application in the background, and create a socket connection to it as soon as it is ready?
Not sure if there's a better way, but I solved this by using retry:
#thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
begin
#socket = TCPSocket.open('localhost', PORT)
rescue Errno::ECONNREFUSED
sleep 0.1
retry
end
This will indefinitely keep trying to establish the connection 10 times a second until it works. A more robust solution might be to use a counter or timer to eventually give up, just in case something is seriously awry.
The full test code looks like this:
require 'socket'
require 'minitest/autorun'
PORT = 7331
CMD = File.expand_path("../../bin/rb3jay",__FILE__)
class TestServer < MiniTest::Unit::TestCase
def setup
#pid = Process.spawn "#{CMD} -D --port #{PORT}"
begin
#socket = TCPSocket.open('localhost', PORT)
rescue Errno::ECONNREFUSED
sleep 0.1
retry
end
end
def teardown
if #socket && !#socket.closed?
#socket.puts("quit") # try for a nice shutdown
#socket.close
end
Process.kill("HUP",#pid)
end
def test_aaa
# my test code
end
def test_bbb
# more test code
end
end
The problem is that from the main thread you can't know when Thread.new code block has actually been executed. By using sleep, you just give it enough time so it is executed.
In such cases, I prefer to use a Queue, where the Thread.new block does a push (usually a nil) into it after it has finished doing what it's supposed to do, while the thread that used to sleep, does a pop from it. pop waits until there are data available in the Queue.
require 'socket'
PORT = 7331
CMD = File.expand_path("../../bin/rb3jay",__FILE__)
q = Queue.new
#thread = Thread.new do
Process.spawn "#{CMD} -D --port #{PORT}"
q.push(nil)
end
q.pop
#socket = TCPSocket.open('localhost', PORT)
However, you might face problems, since spawning your command doesn't mean that the server is actually ready (listening for new connections). So, I'd try an approach were I could have more control over the life-cycle of the server.
require 'socket'
PORT = 7331
q = Queue.new
#thread = Thread.new do
server = TCPServer.new PORT
q.push(nil)
loop do
client = server.accept
client.puts "Hello !"
client.puts "Time is #{Time.now}"
client.close
end
end
q.pop
#socket = TCPSocket.open('localhost', PORT)

web server in ruby and connection keep-alive

Web server example:
require 'rubygems'
require 'socket'
require 'thread'
class WebServer
LINE_TERMINATOR = "\r\n".freeze
def initialize(host, port)
#server = TCPServer.new(host, port)
end
def run
response_body = 'Hello World!'.freeze
response_headers = "HTTP/1.1 200 OK#{LINE_TERMINATOR}Connection: Keep-Alive#{LINE_TERMINATOR}Content-Length: #{response_body.bytesize}#{LINE_TERMINATOR}".freeze
loop do
Thread.new(#server.accept) do |socket|
puts "request #{socket}"
sleep 3
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket.write(response_headers)
socket.write(LINE_TERMINATOR)
socket.write(response_body)
# socket.close # if this line is uncommented then it's work.
end
end
end
end
WebServer.new('localhost', 8888).run
if update browser without waiting for the end of the cycle then the following queries are not processed
How can handle incomming request which are persistent socket ?
You need to:
Keep around the sockets you get from the #server.accept call. Store them in an array (socket_array).
Use the IO.select call on the array of sockets to get the set of sockets that can be read:
ready = IO.select(socket_array)
readable = ready[0]
readable.each do |socket|
# Read from socket here
# Do the rest of processing here
Don't close the socket after you have sent the data.
If you need more details leave a comment - I can write more of the code.

Ctrl+C not killing Sinatra + EM::WebSocket servers

I'm building a Ruby app that runs both an EM::WebSocket server as well as a Sinatra server. Individually, I believe both of these are equipped to handle a SIGINT. However, when running both in the same app, the app continues when I press Ctrl+C. My assumption is that one of them is capturing the SIGINT, preventing the other from capturing it as well. I'm not sure how to go about fixing it, though.
Here's the code in a nutshell:
require 'thin'
require 'sinatra/base'
require 'em-websocket'
EventMachine.run do
class Web::Server < Sinatra::Base
get('/') { erb :index }
run!(port: 3000)
end
EM::WebSocket.start(port: 3001) do |ws|
# connect/disconnect handlers
end
end
I had the same issue. The key for me seemed to be to start Thin in the reactor loop with signals: false:
Thin::Server.start(
App, '0.0.0.0', 3000,
signals: false
)
This is complete code for a simple chat server:
require 'thin'
require 'sinatra/base'
require 'em-websocket'
class App < Sinatra::Base
# threaded - False: Will take requests on the reactor thread
# True: Will queue request for background thread
configure do
set :threaded, false
end
get '/' do
erb :index
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") {
puts "Shutting down"
EventMachine.stop
}
Signal.trap("TERM") {
puts "Shutting down"
EventMachine.stop
}
#clients = []
EM::WebSocket.start(:host => '0.0.0.0', :port => '3001') do |ws|
ws.onopen do |handshake|
#clients << ws
ws.send "Connected to #{handshake.path}."
end
ws.onclose do
ws.send "Closed."
#clients.delete ws
end
ws.onmessage do |msg|
puts "Received message: #{msg}"
#clients.each do |socket|
socket.send msg
end
end
end
Thin::Server.start(
App, '0.0.0.0', 3000,
signals: false
)
end
I downgrade thin to version 1.5.1 and it just works. Wired.

Resources