EM::WebSocket.run as well as INotify file-watching - ruby

This is the code I currently have:
#!/usr/bin/ruby
require 'em-websocket'
$cs = []
EM.run do
EM::WebSocket.run(:host => "::", :port => 8085) do |ws|
ws.onopen do |handshake|
$cs << ws
end
ws.onclose do
$cs.delete ws
end
end
end
I would like to watch a file with rb-inotify and send a message to all connected clients ($cs.each {|c| c.send "File changed"}) when a file changes. The problem is, I do not understand EventMachine, and I can't seem to find a good tutorial.
So if anyone could explain to me where to put the rb-inotify-related code, I would really appreciate it.

Of course! As soon as I post the question, I figure it out!
#!/usr/bin/ruby
require 'em-websocket'
$cs = []
module Handler
def file_modified
$cs.each {|c| c.send "File was modified!" }
end
end
EM.run do
EM.watch_file("/tmp/foo", Handler)
EM::WebSocket.run(:host => "::", :port => 8085) do |ws|
ws.onopen do |handshake|
$cs << ws
end
ws.onclose do
$cs.delete ws
end
end
end

Related

How can i implement secure websocket client in ruby?

How can i create WSS client connection in JRuby framework?
i am using faye/websocket-driver-ruby gem for connecting and publishing message, can anyone provide the imformation about this.
Getting error,
IOError: Connection reset by peer
sysread at org/jruby/ext/openssl/SSLSocket.java:857,
Please let me know what i am doing wrong.
require 'bundler/setup'
require 'eventmachine'
require 'websocket/driver'
require 'permessage_deflate'
require 'socket'
require 'uri'
require "openssl"
class WSSClient
DEFAULT_PORTS = { 'ws' => 80, 'wss' => 443 }
attr_reader :url, :thread
def initialize
#url = "wss://localhost:433/wss"
#uri = URI.parse(#url)
#port = #uri.port || DEFAULT_PORTS[#uri.scheme]
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
#socket = TCPSocket.new(#uri.host, #port)
ssl_context.ssl_version = :TLSv1_2_client
ssl_socket = OpenSSL::SSL::SSLSocket.new(#socket, ssl_context)
ssl_socket.sync_close = true
ssl_socket.connect
#driver = WebSocket::Driver.client(self)
#driver.add_extension(PermessageDeflate)
str = "Hello server!"
str = str + "\n"
#dead = false
#driver.on(:open) { |event| write str }
#driver.on(:message) { |event| p [:message, event.data] }
#driver.on(:close) { |event| finalize(event) }
#thread = Thread.new do
#driver.parse(ssl_socket.read(1)) until #dead
end
#driver.start
end
def send(message)
#driver.text(message)
end
def write(data)
ssl_socket.write(data)
end
def close
#driver.close
end
def finalize(event)
p [:close, event.code, event.reason]
#dead = true
#thread.kill
end
end
Or any other way (algorithm) to create WSS client.

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.

Ruby Event Machine stop or kill deffered operation

I was wondering if I could stop execution of an operation that has been deffered.
require 'rubygems'
require 'em-websocket'
EM.run do
EM::WebSocket.start(:host => '0.0.0.0', :port => 8080) do |ws|
ws.onmessage do |msg|
op = proc do
sleep 5 # Thread safe IO here that is safely killed
true
end
callback = proc do |result|
puts "Done!"
end
EM.defer(op, callback)
end
end
end
This is an example web socket server. Sometimes when I get a message I want to do some IO, later on another message might come in that needs to read the same thing, the next thing always has precedence over the previous thing. So I want to cancel the first op and do the second.
Here is my solution. It is similar to the EM.queue solution, but just uses a hash.
require 'rubygems'
require 'em-websocket'
require 'json'
EM.run do
EM::WebSocket.start(:host => '0.0.0.0', :port => 3333) do |ws|
mutex = Mutex.new # to make thread safe. See https://github.com/eventmachine/eventmachine/blob/master/lib/eventmachine.rb#L981
queue = EM::Queue.new
ws.onmessage do |msg|
message_type = JSON.parse(msg)["type"]
op = proc do
mutex.synchronize do
if message_type == "preferred"
puts "killing non preferred\n"
queue.size.times { queue.pop {|thread| thread.kill } }
end
queue << Thread.current
end
puts "doing the long running process"
sleep 15 # Thread safe IO here that is safely killed
true
end
callback = proc do |result|
puts "Finished #{message_type} #{msg}"
end
EM.defer(op, callback)
end
end
end

em-http-request unexpected result when using tor as proxy

I've created a gist which shows exactly what happens.
https://gist.github.com/4418148
I've tested a version which used ruby's 'net/http' library and 'socksify/http' and it worked perfect but if the EventMachine version returns an unexpected result.
The response in Tor Browser is correct but using EventMachine is not!
It return a response but it's not the same as returned response when you send the request via browser, net/http with or without proxy.
For convenience, I will also paste it here.
require 'em-http-request'
DEL = '-'*40
#results = 0
def run_with_proxy
connection_opts = {:proxy => {:host => '127.0.0.1', :port => 9050, :type => :socks5}}
conn = EM::HttpRequest.new("http://www.apolista.de/tegernsee/kloster-apotheke", connection_opts)
http = conn.get
http.callback {
if http.response.include? "Oops"
puts "#{DEL}failed with proxy#{DEL}", http.response
else
puts "#{DEL}success with proxy#{DEL}", http.response
end
#results+=1
EM.stop_event_loop if #results == 2
}
end
def run_without_proxy
conn = EM::HttpRequest.new("http://www.apolista.de/tegernsee/kloster-apotheke")
http = conn.get
http.callback {
if http.response.include? "Oops"
puts "#{DEL}failed without proxy#{DEL}", http.response
else
puts "#{DEL}success without proxy#{DEL}", http.response
end
#results+=1
EM.stop_event_loop if #results == 2
}
end
EM.run do
run_with_proxy
run_without_proxy
end
Appreciate any clarification.

How do I run Net::SSH and AMQP in the same EventMachine reactor?

Some background: Gerrit exposes an event stream through SSH. It's a cute trick, but I need to convert those events into AMQP messages. I've tried to do this with ruby-amqp and Net::SSH but, well, it doesn't seem as if the AMQP sub-component is even being run at all.
I'm fairly new to EventMachine. Can someone point out what I am doing incorrectly? The answer to "Multiple servers in a single EventMachine reactor) didn't seem applicable. The program, also available in a gist for easier access, is:
#!/usr/bin/env ruby
require 'rubygems'
require 'optparse'
require 'net/ssh'
require 'json'
require 'yaml'
require 'amqp'
require 'logger'
trap(:INT) { puts; exit }
options = {
:logs => 'kili.log',
:amqp => {
:host => 'localhost',
:port => '5672',
},
:ssh => {
:host => 'localhost',
:port => '22',
:user => 'nobody',
:keys => '~/.ssh/id_rsa',
}
}
optparse = OptionParser.new do|opts|
opts.banner = "Usage: kili [options]"
opts.on( '--amqp_host HOST', 'The AMQP host kili will connect to.') do |a|
options[:amqp][:host] = a
end
opts.on( '--amqp_port PORT', 'The port for the AMQP host.') do |ap|
options[:amqp][:port] = ap
end
opts.on( '--ssh_host HOST', 'The SSH host kili will connect to.') do |s|
options[:ssh][:host] = s
end
opts.on( '--ssh_port PORT', 'The SSH port kili will connect on.') do |sp|
options[:ssh][:port] = sp
end
opts.on( '--ssh_keys KEYS', 'Comma delimeted SSH keys for user.') do |sk|
options[:ssh][:keys] = sk
end
opts.on( '--ssh_user USER', 'SSH user for host.') do |su|
options[:ssh][:user] = su
end
opts.on( '-l', '--log LOG', 'The log location of Kili') do |log|
options[:logs] = log
end
opts.on( '-h', '--help', 'Display this screen' ) do
puts opts
exit
end
end
optparse.parse!
log = Logger.new(options[:logs])
log.level = Logger::INFO
amqp = options[:amqp]
sshd = options[:ssh]
queue= EM::Queue.new
EventMachine.run do
AMQP.connect(:host => amqp[:host], :port => amqp[:port]) do |connection|
log.info "Connected to AMQP at #{amqp[:host]}:#{amqp[:port]}"
channel = AMQP::Channel.new(connection)
exchange = channel.topic("traut", :auto_delete => true)
queue.pop do |msg|
log.info("Pulled #{msg} out of queue.")
exchange.publish(msg[:data], :routing_key => msg[:route]) do
log.info("On route #{msg[:route]} published:\n#{msg[:data]}")
end
end
end
Net::SSH.start(sshd[:host], sshd[:user],
:port => sshd[:port], :keys => sshd[:keys].split(',')) do |ssh|
log.info "SSH connection to #{sshd[:host]}:#{sshd[:port]} as #{sshd[:user]} made."
channel = ssh.open_channel do |ch|
ch.exec "gerrit stream-events" do |ch, success|
abort "could not stream gerrit events" unless success
# "on_data" is called when the process writes something to
# stdout
ch.on_data do |c, data|
json = JSON.parse(data)
if json['type'] == 'change-merged'
project = json['change']['project']
route = "com.carepilot.event.code.review.#{project}"
msg = {:data => data, :route => route}
queue.push(msg)
log.info("Pushed #{msg} into queue.")
else
log.info("Ignoring event of type #{json['type']}")
end
end
# "on_extended_data" is called when the process writes
# something to stderr
ch.on_extended_data do |c, type, data|
log.error(data)
end
ch.on_close { log.info('Connection closed') }
end
end
end
end
Net::SSH is not asynchronous, so your EventMachine.run() is never reaching the end of the block, thus never resuming the reactor thread. This causes the AMQP code to never start. I would suggest running your SSH code within another thread.
If you go back to EventMachine, give em-ssh https://github.com/simulacre/em-ssh a try.

Resources