Class: MarilynRPC::NativeClient

Inherits:
Object
  • Object
show all
Defined in:
lib/marilyn-rpc/client.rb

Overview

The client that will handle the socket to the remote. The native client is written in pure ruby.

Examples:

Using the native client

require "marilyn-rpc"
client = MarilynRPC::NativeClient.connect_tcp('localhost', 8483)
TestService = client.for(:test)
TestService.add(1, 2)
TestService.time.to_f

Constant Summary collapse

MAIL_KEY =
:_mlynml

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(socket) ⇒ NativeClient

Create a native client for the socket.

Parameters:

  • socket (Socket)

    the socket to manage



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/marilyn-rpc/client.rb', line 39

def initialize(socket)
  @socket = socket
  @semaphore = Mutex.new
  @threads = {}
  @thread = Thread.new do
    # read the answer of the server back in
    envelope = MarilynRPC::Envelope.new
    loop do
      # read the header to have the size
      envelope.parse_header! @socket.read(MarilynRPC::Envelope::HEADER_SIZE)
      # so now that we know the site, read the rest of the envelope without
      # parsing
      envelope.content = @socket.read(envelope.size)

      # returns the result part of the mail or raise the exception if there is 
      # one
      mail = MarilynRPC::MailFactory.unpack(envelope)
      thread = @semaphore.synchronize { @threads.delete(mail.tag) }
      thread[MAIL_KEY] = mail # save the mail for the waiting thread
      thread.wakeup # wake up the waiting thread
      envelope.reset!
    end
  end
end

Class Method Details

.connect_tcp(host, port, options = {}) ⇒ MarilynRPC::NativeClient

Connect to a tcp socket.

Parameters:

  • host (String)

    the host to cennect to (e.g. ‘localhost’)

  • port (Integer)

    the port to connect to (e.g. 8000)

  • options (Hash) (defaults to: {})

    the

Options Hash (options):

  • :secure (Boolean)

    use tls/ssl for the connection ‘true` or `false`

  • :ssl_context (OpenSSL::SSL::SSLContext)

    can be used to change the ssl context of the newly created secure connection. Only takes effect if ‘:secure` option is enabled.

Returns:



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/marilyn-rpc/client.rb', line 105

def self.connect_tcp(host, port, options = {})
  if options[:secure] == true
    require 'openssl' # use openssl for secure connections
    socket = TCPSocket.new(host, port)
    if ssl_context = options[:ssl_context]
      secure_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
    else
      secure_socket = OpenSSL::SSL::SSLSocket.new(socket)
    end
    secure_socket.connect
    new(secure_socket)
  else
    new(TCPSocket.open(host, port))
  end
end

.connect_unix(path) ⇒ MarilynRPC::NativeClient

Connect to a unix domain socket.

Parameters:

  • path (String)

    the path to the socket file.

Returns:



91
92
93
# File 'lib/marilyn-rpc/client.rb', line 91

def self.connect_unix(path)
  new(UNIXSocket.new(path))
end

Instance Method Details

#authenticate(username, password, method = :plain) ⇒ Object

authenicate the client to call methods that require authentication

Parameters:

  • username (String)

    the username of the client

  • password (String)

    the password of the client

  • method (Symbol) (defaults to: :plain)

    the method to use for authentication, currently only plain is supported. So make sure you are using a secure socket.



74
75
76
77
# File 'lib/marilyn-rpc/client.rb', line 74

def authenticate(username, password, method = :plain)
  execute(MarilynRPC::Service::AUTHENTICATION_PATH,
          "authenticate_#{method}".to_sym, [username, password])
end

#disconnectObject

Disconnect the client from the remote.



65
66
67
# File 'lib/marilyn-rpc/client.rb', line 65

def disconnect
  @socket.close
end

#execute(path, method, args) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Executes a client call blocking. To issue an async call one needs to have start separate threads. THe Native client uses then multiplexing to avoid the other threads blocking.

Parameters:

  • path (Object)

    the path to identifiy the service

  • method (Symbol, String)

    the method name to call on the service

  • args (Array<Object>)

    the arguments that are passed to the remote side

Returns:

  • (Object)

    the result of the call



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/marilyn-rpc/client.rb', line 130

def execute(path, method, args)
  thread = Thread.current
  tag = "#{Time.now.to_f}:#{thread.object_id}"
  
  @semaphore.synchronize {
    # since this client can't multiplex, we set the tag to nil
    @socket.write(MarilynRPC::MailFactory.build_call(tag, path, method, args))
    
    # lets write our self to the list of waining threads
    @threads[tag] = thread
  }
  
  # stop the current thread, the thread will be started after the response
  # arrived
  Thread.stop

  # get mail from responses
  mail = thread[MAIL_KEY]

  if mail.is_a? MarilynRPC::CallResponseMail
    mail.result
  else
    raise MarilynError.new # raise exception to capture the client backtrace
  end
rescue MarilynError => exception
  # add local and remote trace together and reraise the original exception
  backtrace = []
  backtrace += exception.backtrace
  backtrace += mail.exception.backtrace
  mail.exception.set_backtrace(backtrace)
  raise mail.exception
end

#for(path) ⇒ MarilynRPC::NativeClientProxy

Creates a new Proxy Object for the connection.

Parameters:

  • path (Object)

    the path were the service is registered on the remote site

Returns:



84
85
86
# File 'lib/marilyn-rpc/client.rb', line 84

def for(path)
  NativeClientProxy.new(path, self)
end