Paul
Paul

Reputation: 26660

In-memory stream for Ruby

When working with Protocol Buffers the real message size becomes known when a whole object is written to IO. So I use following approach: write object to intermediate stream, get it's size and then write whole data, with header containing size int, to TCP socket.

What I do not like in following code is the message_size function which uses real disk file instead of memory stream.

require 'protocol_buffers'

module MyServer
  class AuthRequest < ProtocolBuffers::Message
    required :int32, :vers, 1
    required :int32, :orgID, 2
    required :string, :password, 3
  end

  class MyServer
    def self.authenticate(socket, params)
      auth = AuthRequest.new(:vers => params[:vers], :orgID => params[:orgID], :password => params[:password])
      size = message_size(auth)
      if size.present?
        socket.write([size, 0].pack 'NN')
        auth.serialize(socket)
        socket.flush
      end
    end

    def self.message_size(obj)
      size = nil
      io = File.new('tempfile', 'w')
      begin
        obj.serialize(io)
        io.flush
        size = io.stat.size + 4
      ensure
        io.close
      end
      size
    end
  end
end

Controller:

require 'my_server'
require 'socket'

class MyServerTestController < ActionController::Base
  def test
    socket = TCPSocket.new('192.168.1.15', '12345')
    begin
      MyServer::MyServer.authenticate(socket, {vers: 1, orgID: 100, password: 'hello'})
    ensure
      socket.close
    end
  end
end

Upvotes: 1

Views: 3423

Answers (1)

Uri Agassi
Uri Agassi

Reputation: 37409

You can easily use StringIO as your memory stream. Mind you, it is called StringIO since it is implemented on a string, and it is definitely not bound for it being string data - it works just as easily on binary data:

def self.message_size_mem(obj)
  size = nil
  io = StringIO.new
  begin
    obj.serialize(io)
    io.flush
    size = io.size + 4
  ensure
    io.close
  end
  size
end


auth = AuthRequest.new(:vers => 122324, orgID: 9900235, password: 'this is a test for serialization')
MyServer.message_size(auth)
# => 47
MyServer.message_size_mem(auth)
# => 47

io = StringIO.new
auth.serialize(io)
io.flush
io.string
# => "\bԻ\a\u0010ˡ\xDC\u0004\u001A this is a test for serialization"

Upvotes: 3

Related Questions