How to programmatically get Sinatra's active port? - ruby

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

Related

Ruby 2.3 TCP Server Daemon for Postfix SMTP Access Policy Delegation

I'm get stuck to write a tcp server daemon in Ruby 2.3. The issue is, that my connection is not going further, when postfix is communicating with the ruby tcp server. If i do connect to the ruby tcp server by telnet, everything works fine. My code is as follows:
require 'socket'
require_relative 'postfix_delegation_object'
class Server
attr_reader :binding, :port
def initialize(binding: '127.0.0.1', port: '1988')
puts "Starting server now!"
puts "Listening on tcp://#{binding}:#{port}"
socket = TCPServer.new(binding, port)
while client = socket.accept
Thread.new { handle_connection(client) }
end
end
def handle_connection(client)
hash_values = {}
puts "New client! #{client}"
while line = client.gets
if line.include? "="
key, val = line.split('=')
hash_values[key] = val.to_s.strip
else
pdo = PostfixDelegationObject.new(hash_values)
client.write("action=dunno")
end
end
end
end
I could solve it by my own. I just had to enter twice '\n'
like this:
client.write("action=dunno\n\n")
This one would not work:
client.write("action=dunno")
client.write("\n\n")

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.

Sinatra randomly starts demo server

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.

Resources