module GRPCWeb::ClientExecutor

Client execution concerns

Constants

GRPC_HEADERS
GRPC_MESSAGE_HEADER
GRPC_STATUS_HEADER

Public Class Methods

request(uri, rpc_desc, params = {}) click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 19
def request(uri, rpc_desc, params = {})
  req_proto = rpc_desc.input.new(params)
  marshalled_proto = rpc_desc.marshal_proc.call(req_proto)
  frame = ::GRPCWeb::MessageFrame.payload_frame(marshalled_proto)
  request_body = ::GRPCWeb::MessageFraming.pack_frames([frame])

  resp = post_request(uri, request_body)
  resp_body = handle_response(resp)
  rpc_desc.unmarshal_proc(:output).call(resp_body)
end

Private Class Methods

extract_headers(frames) click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 66
def extract_headers(frames)
  header_frame = frames.find(&:header?)
  if header_frame
    parse_headers(header_frame.body)
  else
    {}
  end
end
handle_response(resp) click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 53
def handle_response(resp)
  begin
    frames = ::GRPCWeb::MessageFraming.unpack_frames(resp.body)
    headers = extract_headers(frames)
  rescue StandardError
    headers = {}
    error_unpacking_frames = true
  end
  raise_on_response_errors(resp, headers, error_unpacking_frames)

  frames.find(&:payload?).body
end
parse_headers(header_str) click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 75
def parse_headers(header_str)
  headers = {}
  lines = header_str.split(/\r?\n/)
  lines.each do |line|
    key, value = line.split(':', 2)
    headers[key] = value
  end
  headers
end
post_request(uri, request_body) click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 39
def post_request(uri, request_body)
  request = Net::HTTP::Post.new(uri, request_headers)
  request.body = request_body
  request.basic_auth uri.user, uri.password if uri.userinfo

  begin
    Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
      http.request(request)
    end
  rescue StandardError => e
    raise ::GRPC::Unavailable, e.message
  end
end
raise_on_response_errors(resp, headers, error_unpacking_frames) click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 85
def raise_on_response_errors(resp, headers, error_unpacking_frames)
  metadata = headers.reject { |key, _| GRPC_HEADERS.include?(key) }
  status_str = headers[GRPC_STATUS_HEADER]
  status_code = status_str.to_i if status_str && status_str == status_str.to_i.to_s

  # see https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
  if status_code && status_code != 0
    raise ::GRPC::BadStatus.new_status_exception(
      status_code,
      headers[GRPC_MESSAGE_HEADER],
      metadata,
    )
  end

  case resp
  when Net::HTTPBadRequest # 400
    raise ::GRPC::Internal.new(resp.message, metadata)
  when Net::HTTPUnauthorized # 401
    raise ::GRPC::Unauthenticated.new(resp.message, metadata)
  when Net::HTTPForbidden # 403
    raise ::GRPC::PermissionDenied.new(resp.message, metadata)
  when Net::HTTPNotFound # 404
    raise ::GRPC::Unimplemented.new(resp.message, metadata)
  when Net::HTTPTooManyRequests, # 429
      Net::HTTPBadGateway, # 502
      Net::HTTPServiceUnavailable, # 503
      Net::HTTPGatewayTimeOut # 504
    raise ::GRPC::Unavailable.new(resp.message, metadata)
  else
    raise ::GRPC::Unknown.new(resp.message, metadata) unless resp.is_a?(Net::HTTPSuccess) # 200
    raise ::GRPC::Internal.new(resp.message, metadata) if error_unpacking_frames
    raise ::GRPC::Unknown.new(resp.message, metadata) if status_code.nil?
  end
end
request_headers() click to toggle source
# File lib/grpc_web/client/client_executor.rb, line 32
def request_headers
  {
    'Accept' => PROTO_CONTENT_TYPE,
    'Content-Type' => PROTO_CONTENT_TYPE,
  }
end