class Rack::Remote

Rack::Remote is a Rack middleware for intercepting calls and invoking remote calls. It can be used to call remote function for test instructions in distributed systems.

Constants

VERSION

Public Class Methods

add(name, options = {}) click to toggle source

Add a new remote to be used in ‘invoke` by symbolic reference.

# File lib/rack/remote.rb, line 106
def add(name, options = {})
  raise ArgumentError unless options[:url]
  remotes[name.to_sym] = options
end
calls() click to toggle source

Return hash with registered calls.

# File lib/rack/remote.rb, line 94
def calls
  @calls ||= {}
end
clear() click to toggle source

Removes all registered calls.

# File lib/rack/remote.rb, line 99
def clear
  calls.clear
  remotes.clear
end
invoke(remote, call, params = {}, headers = {}) click to toggle source

Invoke remote call.

@param remote [Symbol, String, to_s] Symbolic remote name or remote URL. @param call [String, to_s] Remote call to invoke. @param params [Hash] Key-Value pairs that will be converted to json and sent to remote call. @param headers [Hash] Header added to request.

# File lib/rack/remote.rb, line 122
def invoke(remote, call, params = {}, headers = {})
  remote = remotes[remote][:url] if remote.is_a? Symbol
  uri = URI.parse remote.to_s
  uri.path = '/' if uri.path.empty?

  Net::HTTP.start uri.host, uri.port do |http|
    request = Net::HTTP::Post.new uri.path
    headers.each do |key, value|
      request[key] = value.to_s
    end

    request['X-Rack-Remote-Call'] = call.to_s
    request['Content-Type'] = 'application/json'
    request.body = MultiJson.dump(params)

    response = http.request request
    if response.code.to_i == 500 and response['Content-Type'] == 'application/json'
      json = MultiJson.load(response.body)

      if json['error'] && json['backtrace'] && json['class']
        remote_error = RemoteError.new class: json['class'], error: json['error'], backtrace: json['backtrace']
        raise Rack::Remote::RemoteCallFailed.new("Remote call returned error code #{response.code}", cause: remote_error)
      end
    end

    raise StandardError, "Rack Remote Error Response: #{response.code}: #{response.body}" if response.code.to_i != 200

    if response['Content-Type'] == 'application/json'
      response.body.empty? ? {} : MultiJson.load(response.body)
    else
      response.body
    end
  end
end
new(app) click to toggle source
# File lib/rack/remote.rb, line 45
def initialize(app)
  @app = app
end
register(name, &block) click to toggle source

Register a new remote call. Used on server side to define available remote calls.

@example

Rack::Remote.register :factory_girl do |env, request|
  FactoryGirl.create request.params[:factory]
end

@params name [String, to_s] Remote call name

# File lib/rack/remote.rb, line 88
def register(name, &block)
  calls[name.to_s] = block
end
remotes() click to toggle source
# File lib/rack/remote.rb, line 111
def remotes
  @remotes ||= {}
end

Public Instance Methods

call(env) click to toggle source
# File lib/rack/remote.rb, line 49
def call(env)
  return @app.call(env) unless env['HTTP_X_RACK_REMOTE_CALL']

  request = ::Rack::Request.new(env)
  call    = env['HTTP_X_RACK_REMOTE_CALL'].to_s

  if (cb = self.class.calls[call])
    begin
      # First rewind request body before read
      request.body.rewind

      data = request.body.read
      json = data.empty? ? {} : MultiJson.load(data)

      response = cb.call(json, env, request)
      if response.is_a?(Array) && response.size == 3
        return response
      else
        [200, {'Content-Type' => 'application/json'}, StringIO.new(MultiJson.dump response) ]
      end
    rescue => err
      [500, {'Content-Type' => 'application/json'}, StringIO.new(MultiJson.dump error: err.message, backtrace: err.backtrace, class: err.class.name) ]
    end
  else
    [404, {'Content-Type' => 'application/json'}, StringIO.new(MultiJson.dump error: 'remote call not defined', calls: call, list: self.class.calls.keys) ]
  end
end