Can I send an object via TCP using Ruby? - ruby

I'm trying to send and "message" object via TCP on Ruby and my client class simply don't see any thing comming. What am I doing wrong?
My message class (that I'm trying to send)
class Message
attr_reader :host_type, :command, :params
attr_accessor :host_type, :command, :params
def initialize(host_type, command, params)
#host_type = host_type
#command = command
#params = params
end
end
My "Server" class
require 'socket'
require_relative 'message'
class TCP_connection
def start_listening
puts "listening"
socket = TCPServer.open(2000)
loop {
Thread.start(socket.accept) do |message|
puts message.command
end
}
end
def send_message
hostname = 'localhost'
port = 2000
s = TCPSocket.open(hostname, port)
message = Message.new("PARAM A", "PARAM B", "PARAM C")
s.print(message)
s.close
end
end

Below an example of client server comunication via json. You will expect something like this RuntimeError: {"method1"=>"param1"}. Instead of raising errors, process this json with the server logic.
Server
require 'socket'
require 'json'
server = TCPServer.open(2000)
loop {
client = server.accept
params = JSON.parse(client.gets)
raise params.inspect
}
Client
require 'socket'
require 'json'
host = 'localhost'
port = 2000
s = TCPSocket.open(host, port)
request = { 'method1' => 'param1' }.to_json
s.print(request)
s.close

You need to serialize your object. The most portable way to do this is using YAML. First, require the yaml library:
require "yaml"
Than, replace s.print(message) with s.print(YAML.dump(message)).

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

ruby 1.9.3 simple GET request to unicorn through socket

Now I try to connect to my socket cretaed by unicorn with this code
require 'socket'
def foo
socket = UNIXSocket.new("path_to_socket/tmp/unicorn.sock")
data = "GET /time HTTP/1.1\n"
data << "Connection: Close\n"
data << "User-Agent: Mozilla/5.0\n"
data << "Accept: */*\n"
data << "Content-Type: application/x-www-form-urlencoded\n"
data << "\n\r\n\r"
socket.puts(data)
while(line = socket.gets) do
puts line
end
end
foo
But always get a "HTTP/1.1 400 Bad Request"
Please, can any body say what I'm do wrong???
Use net/http...
require "net/http"
require "socket"
sock = Net::BufferedIO.new(UNIXSocket.new("path_to_socket/tmp/unicorn.sock"))
request = Net::HTTP::Get.new("/time")
request.exec(sock, "1.1", "/time")
begin
response = Net::HTTPResponse.read_new(sock)
end while response.kind_of?(Net::HTTPContinue)
response.reading_body(sock, request.response_body_permitted?) { }
response.body
response.code
This is very helpful but please note that the Net::HTTP#exec method is marked for internal use only. Likely because it doesn't do resource management, etc.
The following work adapts the suggested strategy to override Net::HTTP#connect (to connect to a socket). I like to use the HTTParty gem for handling my HTTP requests. So the strategy here makes use of a custom ConnectionAdaptor for HTTParty. Now I can just change the ::default_params= call on my including class, to control whether we're using a Unix or a TCP/HTTP socket.
###########################################################
# net/socket_http.rb
###########################################################
module Net
# Overrides the connect method to simply connect to a unix domain socket.
class SocketHttp < HTTP
attr_reader :socket_path
# URI should be a relative URI giving the path on the HTTP server.
# socket_path is the filesystem path to the socket the server is listening to.
def initialize(uri, socket_path)
#socket_path = socket_path
super(uri)
end
# Create the socket object.
def connect
#socket = Net::BufferedIO.new UNIXSocket.new socket_path
on_connect
end
# Override to prevent errors concatenating relative URI objects.
def addr_port
File.basename(socket_path)
end
end
end
###########################################################
# sock_party.rb, a ConnectionAdapter class
###########################################################
require "net/http"
require "socket"
class SockParty < HTTParty::ConnectionAdapter
# Override the base class connection method.
# Only difference is that we'll create a Net::SocketHttp rather than a Net::HTTP.
# Relies on :socket_path in the
def connection
http = Net::SocketHttp.new(uri, options[:socket_path])
if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
http.open_timeout = options[:timeout]
http.read_timeout = options[:timeout]
end
if options[:debug_output]
http.set_debug_output(options[:debug_output])
end
if options[:ciphers]
http.ciphers = options[:ciphers]
end
return http
end
end
###########################################################
# class MockSockParty, a really *nix-y HTTParty
###########################################################
class MockSockParty
include HTTParty
self.default_options = {connection_adapter: SockParty, socket_path: '/tmp/thin.sock'}
def party_hard
self.class.get('/client').body
end
end
###########################################################
# sock_party_spec.rb
###########################################################
require 'spec_helper'
describe SockParty do
it "should party until its socks fall off." do
puts MockSockParty.new.party_hard
end
end

EventMachine server and serial-port using SQL

I'm new to Ruby.
I'm trying to make an app that reads from a serial-port and puts values into a sqlite3 database. When a client connects via TCP socket he should recieve values from the db. Values written by the client should be sent via serial-port.
I have two questions regarding my app.
This would open one connection to the db on the main thread(?) and one for each client..
Is there a better way to use sqlite3?
I think i figured this out. sqlite3 is not thread safe by defaul,t so this seems like the way to do it..
How do i write to the serialport in the recieve_data method? Is it okay to make serial a global variable?
#!/usr/bin/env ruby
#
# server_1
require 'rubygems'
require 'eventmachine'
require 'sqlite3'
require 'em-serialport'
require 'json'
module SocketClient
def self.list
#list ||= []
end
def post_init
SocketClient.list << self
#db = SQLite3::Database.new( "data.db" )
values = []
#db.execute("SELECT * FROM values") do |row|
values << {row[0] => row[1]} #id => value
end
self.send_data "#{values.to_json}\n"
p "Client connected"
end
def unbind
SocketClient.list.delete self
#db.close
end
def receive_data data
p data
#How do i send via serialport from here??? serial.send_data data
end
end
db = SQLite3::Database.new( "data.db" )
EM.run{
EM.start_server '0.0.0.0', 8081, SocketClient
serial = EM.open_serial '/dev/tty.usbserial-xxxxxxxx', 9600, 8, 1, 0
serial.on_data do |data|
#Parse data into an array called values
db.execute("UPDATE values SET value = ? WHERE id = ?", values["value"], values["id"])
SocketClient.list.each{ |c| c.send_data "#{values.to_json}\n" }
end
}
db.close
Setup the constructor for your Socket client so that it will receive the shared serial connection.
module SocketClient
def initialize serial
#serial = serial
end
def receive_data data
p data
#serial.send_data data
end
Then pass it when you call EM.start_server
EM.run{
serial = EM.open_serial '/dev/tty.usbserial-xxxxxxxx', 9600, 8, 1, 0
EM.start_server '0.0.0.0', 8081, SocketClient, serial

Pipe data from HTTP GET to HTTP POST/PUT

I'd like to stream data from an HTTP GET request to an HTTP POST or PUT request. I'd prefer to use Ruby and have already made an attempt using EventMachine and EM-HTTP-Request.
Here's my attempt, to be called using:
HttpToS3Stream.new(src_url, dest_bucket, dest_key, aws_access_key_id, aws_secret_access_key)
http_to_s3_stream.rb
require 'em-http-request'
class HttpToS3Stream
def initialize(http_url, s3_bucket, s3_key, s3_access_key_id, s3_secret_access_key)
#http_url = http_url
#s3_bucket = s3_bucket
#s3_key = s3_key
#s3_access_key_id = s3_access_key_id
#s3_secret_access_key = s3_secret_access_key
go
end
private
def go
EM.run {
# initialize get stream, without listener does not start request
#get_stream = HttpGetStream.new(#http_url)
# initialize put stream, send content length, request starts
#put_stream = S3PutStream.new(#s3_bucket, #s3_key, #s3_access_key_id, #s3_secret_access_key, #get_stream.content_length)
# set listener on get stream, starts request, pipes data to put stream
#get_stream.listener = #put_stream
}
end
end
http_get_stream.rb
require 'httparty'
require 'em-http-request'
class HttpGetStream
def initialize(http_url, listener = nil)
#http_url = http_url
self.listener = listener
end
def listener=(listener)
#listener = listener
listen unless #listener.nil?
end
def content_length
response = HTTParty.head(#http_url)
response['Content-length']
end
private
def listen
http = EventMachine::HttpRequest.new(#http_url).get
http.stream do |chunk|
#listener.send_data chunk
end
http.callback do |chunk|
EventMachine.stop
end
end
end
s3_put_stream.rb
require 'em-http-request'
class S3PutStream
def initialize(s3_bucket, s3_key, s3_access_key_id, s3_secret_access_key, content_length = nil)
#s3_bucket = s3_bucket
#s3_key = s3_key
#s3_access_key_id = s3_access_key_id
#s3_secret_access_key = s3_secret_access_key
#content_length = content_length
#bytes_sent = 0
listen
end
def send_data(data)
#bytes_sent += data.length
#http.on_body_data data
end
private
def listen
raise 'ContentLengthRequired' if #content_length.nil?
#http = EventMachine::HttpRequest.new(put_url).put(
:head => {
'Content-Length' => #content_length,
'Date' => Time.now.getutc,
'Authorization' => auth_key
}
)
#http.errback { |error| puts "error: #{error}" }
end
def put_url
"http://#{#s3_bucket}.s3.amazonaws.com/#{#s3_key}"
end
def auth_key
"#{#s3_access_key_id}:#{#s3_secret_access_key}"
end
end
HttpToS3Stream.new(src_url, dest_bucket, dest_key, aws_access_key_id, aws_secret_access_key)
It seems to be working but always stops at 33468 bytes. Not sure what that's about. Now, by passing chunks directly to #listener.send_data, it is processing the entire GET body. However, the upload is not occurring successfully.
How can I get this to work? And is there a name for what I'm trying to do? I'm having trouble searching for more information.
Any help is appreciated.

How to have an eventmachine server just write data?

I need to implement a server which only writes data, doesn't receive it. All of the eventmachine server examples I've found always have the server receive data first, and then respond with data. I need it to just start writing data to a client after a client connects.
I tried just putting a loop in post_init, but that doesn't seem to work... the client connects, the server writes, but the client never seems to receive anything. Suggestions?
The test server:
require 'rubygems'
require 'eventmachine'
require 'time'
module TestServer
def post_init
puts "-- client connected, sending data --"
while true do
send_data "Hello from TestServer\n"
puts "sent #{Time.now.iso8601}"
end
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 4001, TestServer
puts 'running test server on 4001'
}
The test client:
require 'rubygems'
require 'eventmachine'
module Forwarder
def post_init
puts "-- connected to server --"
end
def receive_data data
# none of the following is ever output
puts "in receive_data"
puts data
end
end
EventMachine::run {
EventMachine::connect '127.0.0.1', 4001, Forwarder
}
Thanks...
Thanks to tmm1 on #eventmachine, got this figured out. Client is the same. Server code is:
require 'rubygems'
require 'eventmachine'
require 'time'
module TestServer
def post_init
puts "-- client connected --"
#timer = EM::PeriodicTimer.new(0.1) {
send_data "Hello from TestServer at #{Time.now.iso8601}\n"
}
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 4001, TestServer
puts 'running test server on 4001'
}

Resources