How to run multiple Sinatra instances without anything else? - ruby

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.

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

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

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.

celluloid-io or eventmachine with mosquitto loops

I'm building a small ruby program to run a connection to a MQTT server and subscribe to a channel. I'm using the mosquitto gem which is just a bridge for libmosquitto C library.
I created a very simple implementation of a program that can run with ruby my_prog.rb:
# Dependencies
require File.expand_path(File.join('..', 'environment'), __FILE__)
# MQTT Application
module Pulsr
class MQTT
attr_reader :host, :port, :alive
def initialize(host = 'iot.eclipse.org', port = 1883, alive = 60)
#client ||= Mosquitto::Client.new SecureRandom.hex(8)
Signal.trap(Signal.list.has_key?('INT') ? 'SIGINT' : 'SIGTERM') do
#client.log 'Shutdown'
shutdown
end
#host = host
#port = port
#alive = alive
start
end
private
def on_connect
Proc.new { |return_code|
#client.log "Connected RC #{return_code}"
#client.subscribe(nil, '/pulsr', Mosquitto::EXACTLY_ONCE)
}
end
def on_disconnect
Proc.new { |return_code| #client.log "Disconnected RC #{return_code}" }
end
def on_subscribe
Proc.new { |message_id, granted_qos| #client.log "Subscribed MID #{message_id} QoS #{granted_qos}" }
end
def on_unsubscribe
Proc.new { |message_id| #client.log "Unsubscribed MID #{message_id}" }
end
def on_message
Proc.new { |message| Pulsr::Workers::TrackingEvent.perform_async message.to_s }
end
def configure
#client.logger = Logger.new(STDOUT)
#client.on_connect &on_connect
#client.on_disconnect &on_disconnect
#client.on_subscribe &on_subscribe
#client.on_unsubscribe &on_unsubscribe
#client.on_message &on_message
end
def connect
#client.connect_async(#host, #port, #alive)
end
def start
#client.loop_start
configure
connect
sleep
end
def shutdown
#client.loop_stop(true)
Process.exit
end
end
end
# MQTT Start
Pulsr::MQTT.new :host => 'iot.eclipse.org', :port => 1883, :alive => 60
I was wondering, if I wanted to use Celluloid or EventMachine to run the loops that the mosquitto gem provides, how would I do it?
The mosquitto gem provides a good documentation and presents a few loop methods that can be used, but I have no clue where to start or how to do it, neither I have ever used EM or Celluloid.
Could anyone help get started with this, I think it could bring some value to the community and it can end up as a open source project, a small addition to the mosquitto gem?
I think it is not that hard.
Mosquitto has a good library.
Yo need to connect these functions:
mosquitto_loop_misc() <-> EventMachine::PeriodicTimer.new
mosquitto_read() <-> EventMachine.watch
mosquitto_write() <-> EventMachine.watch
The em-mqtt gem provides an MQTT protocol implementation for eventmachine. This uses the pure ruby mqtt implementation to process the messages rather than libmosquitto.
If you really have to use the libmosquitto implementation for the parsing via the mosquitto gem then the above delineation would hold. The eventmachine component will be pretty much as is. All the calls to the protocol specific MQTT module would be replaced with the equivalent in libmosquitto. The main problem looks to be that the libmosquitto public API and subsequent Ruby API hides all of this away, down in libmosquitto's own network implementation, which is being replaced with eventmachine, so you would have a lot of hacking to expose the required methods to Ruby before you can get started.

Multiple servers in a single EventMachine reactor

Is it possible to run multiple servers within a single event machine?
What I mean is that multiple services can be used concurrently by a single client connection. For example, a login server authenticates a user and then a user can use both a chat room and a simple game such as checkers with a single client socket concurrently?
Or do I need multiple eventmachine reactors for each service?
I tried this and it's working:
#!/usr/bin/env ruby
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 8081, EchoServer
EventMachine::start_server "127.0.0.1", 8082, EchoServer
EventMachine::start_server "127.0.0.1", 8083, EchoServer
}
Here you get 3 echo services with different ports. (I was too lazy to implement different services.)
So, it's very easy to build a huge multi service wrapper.
Update
Simple code example for condition based start of a EM server:
#!/usr/bin/env ruby
# encoding: utf-8
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
$check_ok = false
EventMachine::run {
puts "checker var is: #{$check_ok}"
EventMachine::start_server "127.0.0.1", 8081, EchoServer
EventMachine::start_server "127.0.0.1", 8082, EchoServer
puts "echos on 8081 and 8082 started."
# periodic timer check - every 1 sec
EventMachine.add_periodic_timer(1) {
if $check_ok
EventMachine::start_server "127.0.0.1", 8083, EchoServer
$check_ok = false
puts "echo on 8083 started!"
end
}
# timer triggered after 10 secs - only once!
EventMachine.add_timer(10) {
$check_ok = true
puts "checker var is #{$check_ok} now!"
}
}
In this example the echo server on port 8083 is started ~10 secs after app start. Try to telnet localhost 8083 before and after this timer, you'll see the effect.
You also can use values lower than 1 sec like 0.01 for every 1/100th sec checks.
This might be your starting point for your own ideas. The periodic timer is your internal loop, where you hook in your conditional checks for starting further services.
Good tutorial (PDF): eventmachine introduction (blog post)

Resources