Connecting to background EventMachine application for unit testing - ruby

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)

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

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.

How to run a background thread in ruby?

I am new to ruby and thought it would be a great idea to rebuild a simple chat program I made in C#.
I am using Ruby 2.0.0 MRI (Matz’s Ruby Implementation).
The problem is I want to have I/O for simple server commands while the server is running.
This is the server that was taken from the sample. I added the commands method that uses gets() to get input. I want this method to run as a thread in the background, but the thread is blocking the other thread.
require 'socket' # Get sockets from stdlib
server = TCPServer.open(2000) # Socket to listen on port 2000
def commands
x = 1
while x == 1
exitProgram = gets.chomp
if exitProgram == "exit" || exitProgram == "Exit"
x = 2
abort("Exiting the program.")
end
end
end
def main
Thread.start(commands)
Thread.start(server.accept)
loop { # Servers run forever
Thread.start(server.accept) do |client|
client.puts(Time.now.ctime) # Send the time to the client
client.puts "Closing the connection. Bye!"
client.close # Disconnect from the client
end
}
end
main
This is the client so far.
require 'socket' # Sockets are in standard library
hostname = 'localhost'
port = 2000
s = TCPSocket.open(hostname, port)
while line = s.gets # Read lines from the socket
puts line.chop # And print with platform line terminator
end
s.close # Close the socket when done
gets.chomp
Read the documentation for Thread.new (which is the same as Thread.start here)
Thread.start(commands) runs the commands method and passes its return value to a thread (which then does nothing). It's blocking because you aren't starting any threads when gets is called. You want
Thread.start { commands }
Here's a similar demo script that works just like you would expect
def commands
while gets.strip !~ /^exit$/i
puts "Invalid command"
end
abort "Exiting the program"
end
Thread.start { commands }
loop do
puts "Type exit:"
sleep 2
end

TCP server using Ruby socket works but not using eventmachine

I wrote a TCP server using below code. This is to receive GPS location data via GSM network from a remote GPS sensor.
require 'socket'
server = TCPServer.open(2000) # Listen on port 2000
sockets = [server] # An array of sockets we'll monitor
log = STDOUT # Send log messages to standard out
while true
ready = select(sockets)
readable = ready[0]
readable.each do |socket|
if socket == server
client = server.accept
sockets << client
log.puts "Accepted connection from #{client.peeraddr[2]}"
while msg = client.gets
puts msg
end
else
input = socket.gets
if !input
log.puts "Client on #{socket.peeraddr[2]} disconnected"
sockets.delete(socket)
socket.close
next
end
input.chop!
if (input == "quit")
socket.puts("Bye");
log.puts "Closing connnection to #{socket.peeraddr[2]}"
sockets.delete(socket)
socket.close
else
socket.puts(input.reverse)
end
end
end
end
and then I wrote one using Eventmachine. Code as below:
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
puts data
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 2000, EchoServer
}
However, this eventmachine code will not receive nor display the data. Any part of the Eventmachine code that is wrong?
Thanks
I think your problem is that you are listening on localhost only, try this:
EM::run do
EM.start_server "0.0.0.0", 2000, EchoServer
end

How to disconnect redis client in websocket eventmachine

I'm trying to build a websocket server where each client establish its own redis connections used for publish and subscribe.
When the redis server is running I can see the two new connections being established when a client connects to the websocket server and I can also publish data to the client, but when the client drops the connection to the websocket server I also want to disconnect from Redis . How can I do this?
Maybe I'm doing it wrong, but this is my code.
#require 'redis'
require 'em-websocket'
require 'em-hiredis'
require 'json'
CLIENTS = Hash.new
class PubSub
def initialize(client)
#socket = client.ws
# These clients can only be used for pub sub commands
#publisher = EM::Hiredis.connect #Later I will like to disconnect this
#subscriber = EM::Hiredis.connect #Later I will like to disconnect this
client.connections << #publisher << #subscriber
end
def subscribe(channel)
#channel = channel
#subscriber.subscribe(channel)
#subscriber.on(:message) { |chan, message|
#socket.send message
}
end
def publish(channel,msg)
#publisher.publish(channel, msg).errback { |e|
puts [:publisherror, e]
}
end
def unsubscribe()
#subscriber.unsubscribe(#channel)
end
end
class Client
attr_accessor :connections, :ws
def initialize(ws)
#connections = []
#ws = ws
end
end
EventMachine.run do
# Creates a websocket listener
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8081) do |ws|
ws.onopen do
# I instantiated above
puts 'CLient connected. Creating socket'
#client = Client.new(ws)
CLIENTS[ws] = #client
end
ws.onclose do
# Upon the close of the connection I remove it from my list of running sockets
puts 'Client disconnected. Closing socket'
#client.connections.each do |con|
#do something to disconnect from redis
end
CLIENTS.delete ws
end
ws.onmessage { |msg|
puts "Received message: #{msg}"
result = JSON.parse(msg)
if result.has_key? 'channel'
ps = PubSub.new(#client)
ps.subscribe(result['channel'])
elsif result.has_key? 'publish'
ps = PubSub.new(ws)
ps.publish(result['publish']['channel'],result['publish']['msg']);
end
}
end
end
This version of em-hiredis supports close connection: https://github.com/whatupdave/em-hiredis
Here is how I would (and did many times) this:
instead of always opening and closing connections for each client you can keep 1 connection open per Thread/Fiber dependeing on what you are basing your concurrency on, that way if you are using a poll of Thread/Fibers once each one of them have its connections they will keep it and reuse them.
I did not worked much with websocket until now (I was waiting for a standard implementation) but I am sure you can apply that thinking to it too.
You can also do what rails/activerecord: keeo a pool of redis connection, each time you need to use a connection you request one, use it and realease it, it could look like this:
def handle_request(request)
#redis_pool.get_connection do |c|
# [...]
end
end
before yielding the block a connection is taken from the available ones and after it the connection is marked as free.
This was added to em-hiredis: https://github.com/mloughran/em-hiredis/pull/6

Resources