class Gruf::Client

Abstracts out the calling interface for interacting with gRPC clients. Streamlines calling and provides instrumented response objects that also can contain deserialized error messages via serialized objects transported via the service's trailing metadata.

begin
  client = ::Gruf::Client.new(service: ::Demo::ThingService)
  response = client.call(:GetMyThing, id: 123)
  puts response.message.inspect
rescue Gruf::Client::Error => e
  puts e.error.inspect
end

Utilizes SimpleDelegator to wrap the given service that the client is connecting to, which allows a clean interface to the underlying RPC descriptors and methods.

Attributes

base_klass[R]

@return [Class] The base, friendly name of the service being requested

opts[R]

@return [Hash] A hash of options for the client

service_klass[R]

@return [Class] The class name of the gRPC service being requested

Public Class Methods

new(service:, options: {}, client_options: {}) click to toggle source

Initialize the client and setup the stub

@param [Module] service The namespace of the client Stub that is desired to load @param [Hash] options A hash of options for the client @option options [String] :password The password for basic authentication for the service. @option options [String] :hostname The hostname of the service. Defaults to linkerd. @param [Hash] client_options A hash of options to pass to the gRPC client stub

Calls superclass method
# File lib/gruf/client.rb, line 57
def initialize(service:, options: {}, client_options: {})
  @base_klass = service
  @service_klass = "#{base_klass}::Service".constantize
  @opts = options || {}
  @opts[:password] = @opts.fetch(:password, '').to_s
  @opts[:hostname] = @opts.fetch(:hostname, Gruf.default_client_host)
  @opts[:channel_credentials] = @opts.fetch(:channel_credentials, Gruf.default_channel_credentials)
  @error_factory = Gruf::Client::ErrorFactory.new
  client_options[:timeout] = parse_timeout(client_options[:timeout]) if client_options.key?(:timeout)
  client = "#{service}::Stub".constantize.new(@opts[:hostname], build_ssl_credentials, **client_options)
  super(client)
end

Public Instance Methods

call(request_method, params = {}, metadata = {}, opts = {}, &block) click to toggle source

Call the client's method with given params

@param [String|Symbol] request_method The method that is being requested on the service @param [Hash] params (Optional) A hash of parameters that will be inserted into the gRPC request message that is required for the given above call @param [Hash] metadata (Optional) A hash of metadata key/values that are transported with the client request @param [Hash] opts (Optional) A hash of options to send to the gRPC request_response method @return [Gruf::Response] The response from the server @raise [Gruf::Client::Error|GRPC::BadStatus] If an error occurs, an exception will be raised according to the error type that was returned

# File lib/gruf/client.rb, line 82
def call(request_method, params = {}, metadata = {}, opts = {}, &block)
  request_method = request_method.to_sym
  req = streaming_request?(request_method) ? params : request_object(request_method, params)
  md = build_metadata(metadata)
  call_sig = call_signature(request_method)

  unless call_sig
    raise NotImplementedError, "The method #{request_method} has not been implemented in this service."
  end

  resp, operation = execute(call_sig, req, md, opts, &block)

  raise @error_factory.from_exception(resp.result) unless resp.success?

  Gruf::Response.new(operation: operation, message: resp.result, execution_time: resp.time)
end
timeout() click to toggle source

Returns the currently set timeout on the client stub

@return [Integer|NilClass]

# File lib/gruf/client.rb, line 104
def timeout
  __getobj__.instance_variable_get(:@timeout)
end

Private Instance Methods

build_metadata(metadata = {}) click to toggle source

Build a sanitized, authenticated metadata hash for the given request

@param [Hash] metadata A base metadata hash to build from @return [Hash] The compiled metadata hash that is ready to be transported over the wire

# File lib/gruf/client.rb, line 177
def build_metadata(metadata = {})
  unless opts[:password].empty?
    username = opts.fetch(:username, 'grpc').to_s
    username = username.empty? ? '' : "#{username}:"
    auth_string = Base64.encode64("#{username}#{opts[:password]}")
    metadata[:authorization] = "Basic #{auth_string}".tr("\n", '')
  end
  metadata
end
build_ssl_credentials() click to toggle source

Build the SSL/TLS credentials for the outbound gRPC request

@return [Symbol|GRPC::Core::ChannelCredentials] The generated SSL credentials for the outbound gRPC request

:nocov:

# File lib/gruf/client.rb, line 193
def build_ssl_credentials
  return opts[:channel_credentials] if opts[:channel_credentials]

  cert = nil
  if opts[:ssl_certificate_file]
    cert = File.read(opts[:ssl_certificate_file]).to_s.strip
  elsif opts[:ssl_certificate]
    cert = opts[:ssl_certificate].to_s.strip
  end

  cert ? GRPC::Core::ChannelCredentials.new(cert) : :this_channel_is_insecure
end
call_signature(request_method) click to toggle source

Properly find the appropriate call signature for the GRPC::GenericService given the request method name

@return [Symbol]

# File lib/gruf/client.rb, line 166
def call_signature(request_method)
  desc = rpc_desc(request_method)
  desc&.name ? desc.name.to_s.underscore.to_sym : nil
end
error_deserializer_class() click to toggle source

Return the specified error deserializer class by the configuration

@return [Class] The proper error deserializer class. Defaults to JSON.

# File lib/gruf/client.rb, line 212
def error_deserializer_class
  if Gruf.error_serializer
    Gruf.error_serializer.is_a?(Class) ? Gruf.error_serializer : Gruf.error_serializer.to_s.constantize
  else
    Gruf::Serializers::Errors::Json
  end
end
execute(call_sig, req, metadata, opts = {}, &block) click to toggle source

Execute the given request to the service

@param [Symbol] call_sig The call signature being executed @param [Object] req (Optional) The protobuf request message to send @param [Hash] metadata (Optional) A hash of metadata key/values that are transported with the client request @param [Hash] opts (Optional) A hash of options to send to the gRPC request_response method @return [Array<Gruf::Timer::Result, GRPC::ActiveCall::Operation>]

# File lib/gruf/client.rb, line 128
def execute(call_sig, req, metadata, opts = {}, &block)
  operation = nil
  result = Gruf::Timer.time do
    opts[:return_op] = true
    opts[:metadata] = metadata
    operation = send(call_sig, req, opts, &block)
    operation.execute
  end
  [result, operation]
end
parse_timeout(timeout) click to toggle source

Handle various timeout values and prevent improper value setting

@see GRPC::Core::TimeConsts#from_relative_time @param [mixed] timeout @return [Float] @return [GRPC::Core::TimeSpec]

# File lib/gruf/client.rb, line 228
def parse_timeout(timeout)
  if timeout.nil?
    GRPC::Core::TimeConsts::ZERO
  elsif timeout.is_a?(GRPC::Core::TimeSpec)
    timeout
  elsif timeout.is_a?(Numeric)
    timeout
  elsif timeout.respond_to?(:to_f)
    timeout.to_f
  else
    raise ArgumentError, 'timeout is not a valid value: does not respond to to_f'
  end
end
request_object(request_method, params = {}) click to toggle source

Get the appropriate protobuf request message for the given request method on the service being called

@param [Symbol] request_method The method name being called on the remote service @param [Hash] params (Optional) A hash of parameters that will populate the request object @return [Class] The request object that corresponds to the method being called

# File lib/gruf/client.rb, line 156
def request_object(request_method, params = {})
  desc = rpc_desc(request_method)
  desc&.input ? desc.input.new(params) : nil
end
rpc_desc(request_method) click to toggle source

Get the appropriate RPC descriptor given the method on the service being called

@param [Symbol] request_method The method name being called on the remote service @return [Struct<GRPC::RpcDesc>] Return the given RPC descriptor given the method on the service being called

# File lib/gruf/client.rb, line 145
def rpc_desc(request_method)
  service_klass.rpc_descs[request_method]
end
streaming_request?(request_method) click to toggle source

@param [Symbol] request_method @return [Boolean]

# File lib/gruf/client.rb, line 114
def streaming_request?(request_method)
  desc = rpc_desc(request_method)
  desc && (desc.client_streamer? || desc.bidi_streamer?)
end