Class: Rack::Remote

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/remote.rb,
lib/rack/remote/railtie.rb,
lib/rack/remote/version.rb

Overview

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.

Defined Under Namespace

Classes: ChainedError, Railtie, RemoteCallFailed, RemoteError

Constant Summary collapse

VERSION =
'1.1.0'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Remote

Returns a new instance of Remote.



45
46
47
# File 'lib/rack/remote.rb', line 45

def initialize(app)
  @app = app
end

Class Method Details

.add(name, options = {}) ⇒ Object

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

Raises:

  • (ArgumentError)


106
107
108
109
# File 'lib/rack/remote.rb', line 106

def add(name, options = {})
  raise ArgumentError unless options[:url]
  remotes[name.to_sym] = options
end

.callsObject

Return hash with registered calls.



94
95
96
# File 'lib/rack/remote.rb', line 94

def calls
  @calls ||= {}
end

.clearObject

Removes all registered calls.



99
100
101
102
# File 'lib/rack/remote.rb', line 99

def clear
  calls.clear
  remotes.clear
end

.invoke(remote, call, params = {}, headers = {}) ⇒ Object

Invoke remote call.

Parameters:

  • remote (Symbol, String, #to_s)

    Symbolic remote name or remote URL.

  • call (String, #to_s)

    Remote call to invoke.

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

    Key-Value pairs that will be converted to json and sent to remote call.

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

    Header added to request.



122
123
124
125
126
127
128
129
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
# 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

.register(name, &block) ⇒ Object

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

Examples:

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


88
89
90
# File 'lib/rack/remote.rb', line 88

def register(name, &block)
  calls[name.to_s] = block
end

.remotesObject



111
112
113
# File 'lib/rack/remote.rb', line 111

def remotes
  @remotes ||= {}
end

Instance Method Details

#call(env) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 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