Class: RCRest

Inherits:
Object
  • Object
show all
Defined in:
lib/rc_rest.rb

Overview

Abstract class for implementing REST APIs.

Example

The following methods must be implemented in sublcasses:

initialize

Sets @url to the service enpoint.

check_error

Checks for errors in the server response.

parse_response

Extracts information from the server response.

If you have extra URL paramaters (application id, output type) or need to perform URL customization, override make_url and make_multipart.

class FakeService < RCRest

  class Error < RCRest::Error; end

  def initialize(appid)
    @appid = appid
    @url = URI.parse 'http://example.com/api/'
  end

  def check_error(xml)
    raise Error, xml.elements['error'].text if xml.elements['error']
  end

  def make_url(method, params)
    params[:appid] = @appid
    super method, params
  end

  def parse_response(xml)
    return xml
  end

  def test(query)
    get :test, :q => query
  end

end

Defined Under Namespace

Classes: CommunicationError, Error

Constant Summary collapse

VERSION =

You are using this version of RCRest

'2.2.1'

Instance Method Summary collapse

Constructor Details

#initializeRCRest

Web services initializer.

Concrete web services implementations must set the url instance variable which must be a URI.

Raises:

  • (NotImplementedError)


86
87
88
# File 'lib/rc_rest.rb', line 86

def initialize
  raise NotImplementedError, 'need to implement #intialize and set @url'
end

Instance Method Details

#check_error(xml) ⇒ Object

Must extract and raise an error from xml, an REXML::Document, if any. Must return if no error could be found.

Raises:

  • (NotImplementedError)


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

def check_error(xml)
  raise NotImplementedError
end

#expand_params(params) ⇒ Object

:nodoc:



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rc_rest.rb', line 98

def expand_params(params) # :nodoc:
  expanded_params = []

  params.each do |k,v|
    if v.respond_to? :each and not String === v then
      v.each { |v| expanded_params << [k, v] }
    else
      expanded_params << [k, v]
    end
  end

  expanded_params.sort_by { |k,v| [k.to_s, v.to_s] }
end

#get(method, params = {}) ⇒ Object

Performs a GET request for method method with params. Calls #parse_response on the concrete class with an REXML::Document instance and returns its result.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rc_rest.rb', line 117

def get(method, params = {})
  url = make_url method, params

  url.open do |xml|
    res = REXML::Document.new xml.read

    check_error res

    return parse_response(res)
  end
rescue IOError, SystemCallError, SocketError, Timeout::Error,
       REXML::ParseException => e
  raise CommunicationError.new(e)
rescue OpenURI::HTTPError => e
  begin
    xml = REXML::Document.new e.io.read
    check_error xml
  rescue REXML::ParseException => e
  end
  new_e = CommunicationError.new e
  new_e.message << "\n\nunhandled error:\n#{xml.to_s}"
  raise new_e
end

#make_multipart(params) ⇒ Object

Creates a multipart form post for the Hash of parameters params. Override this then call super if you need to add extra params like an application id, output type, etc.

#make_multipart handles arguments similarly to #make_url.



192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/rc_rest.rb', line 192

def make_multipart(params)
  boundary = (0...8).map { rand(255).to_s 16 }.join '_'
  data = expand_params(params).map do |key, value|
    [ "--#{boundary}",
      "Content-Disposition: form-data; name=\"#{key}\"",
      nil,
      value]
  end

  data << "--#{boundary}--"
  return [boundary, data.join("\r\n")]
end

#make_url(method, params = nil) ⇒ Object

Creates a URI for method method and a Hash of parameters params. Override this then call super if you need to add extra params like an application id, output type, etc.

If the value of a parameter responds to #each, make_url creates a key-value pair per value in the param.

Examples:

If the URL base is:

http://example.com/api/

then:

make_url nil, :a => '1 2', :b => [4, 3]

creates the URL:

http://example.com/api/?a=1%202&b=3&b=4

and

make_url :method, :a => '1'

creates the URL:

http://example.com/api/method?a=1


171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rc_rest.rb', line 171

def make_url(method, params = nil)
  escaped_params = expand_params(params).map do |k,v|
    k = URI.escape(k.to_s).gsub(';', '%3B').gsub('+', '%2B').gsub('&', '%26')
    v = URI.escape(v.to_s).gsub(';', '%3B').gsub('+', '%2B').gsub('&', '%26')
    "#{k}=#{v}"
  end

  query = escaped_params.join '&'

  url = @url + "./#{method}"
  url.query = query
  return url
end

#parse_response(xml) ⇒ Object

Must parse results from xml, an REXML::Document, into something sensible for the API.

Raises:

  • (NotImplementedError)


209
210
211
# File 'lib/rc_rest.rb', line 209

def parse_response(xml)
  raise NotImplementedError
end

#post(method, params = {}) ⇒ Object

Performs a POST request for method method with params. Calls #parse_response on the concrete class with an REXML::Document instance and returns its result.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/rc_rest.rb', line 218

def post(method, params = {})
  url = make_url method, params
  query = url.query
  url.query = nil

  req = Net::HTTP::Post.new url.path
  req.body = query
  req.content_type = 'application/x-www-form-urlencoded'

  res = Net::HTTP.start url.host, url.port do |http|
    http.request req
  end

  xml = REXML::Document.new res.body

  check_error xml

  parse_response xml
rescue SystemCallError, SocketError, Timeout::Error, IOError,
       REXML::ParseException => e
  raise CommunicationError.new(e)
rescue Net::HTTPError => e
  xml = REXML::Document.new e.res.body
  check_error xml
  raise CommunicationError.new(e)
end

#post_multipart(method, params = {}) ⇒ Object

Performs a POST request for method method with params, submitting a multipart form. Calls #parse_response on the concrete class with an REXML::Document instance and returns its result.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/rc_rest.rb', line 250

def post_multipart(method, params = {})
  url = make_url method, {}
  url.query = nil

  boundary, data = make_multipart params

  req = Net::HTTP::Post.new url.path
  req.content_type = "multipart/form-data; boundary=#{boundary}"
  req.body = data

  res = Net::HTTP.start url.host, url.port do |http|
    http.request req
  end

  xml = REXML::Document.new res.body

  check_error xml

  parse_response xml
rescue SystemCallError, SocketError, Timeout::Error, IOError,
       REXML::ParseException => e
  raise CommunicationError.new(e)
rescue Net::HTTPError => e
  xml = REXML::Document.new e.res.body
  check_error xml
  raise CommunicationError.new(e)
end