crohr-restfully 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,8 +1,76 @@
1
1
  = restfully
2
2
 
3
- An attempt at dynamically providing "clever" wrappers on top of RESTful APIs that follow the HATEOAS principle. For it to work, the API must follow certain conventions (to be explained later).
3
+ An attempt at dynamically providing "clever" wrappers on top of RESTful APIs that follow the principle of Hyperlinks As The Engine Of Application State (HATEOAS). For it to work, the API must follow certain conventions (to be explained later).
4
4
 
5
- Alpha work, will probably only work against my API of reference :-)
5
+ Alpha work.
6
+
7
+ == Installation
8
+ $ gem install restfully
9
+
10
+ == Usage
11
+ === Command line
12
+ $ resfully api_base_uri relative_root_uri [-u username] [-p password]
13
+
14
+ e.g., for the Grid5000 API:
15
+ $ restfully https://api.grid5000.fr/sid /grid5000 -u username -p password
16
+
17
+ If the connection was successful, you should get a prompt. Call the +root+ function to see what are the available resources. <tt>@property</tt> means that you can call the +property+ method on the object. Other properties are available via the <tt>[]</tt> function.
18
+ e.g.:
19
+ irb(main):005:0> root
20
+ => #<Restfully::Resource:0x90bdd4
21
+ ------------ META ------------
22
+ @uri: "/grid5000"
23
+ @uid: "grid5000"
24
+ @type: "grid"
25
+ @environments: Restfully::Collection
26
+ @sites: Restfully::Collection
27
+ @version: Restfully::Resource
28
+ @versions: Restfully::Collection
29
+ ------------ PROPERTIES ------------
30
+ "version" => "ee1f8111c8835496a9e4a6f7c9491c0238ee9827">
31
+ irb(main):006:0> root.uri
32
+ => "/grid5000"
33
+ irb(main):007:0> root.sites
34
+ => #<Restfully::Collection:0x8faaf2
35
+ ------------ META ------------
36
+ @uri: "/grid5000/sites"
37
+ ------------ PROPERTIES ------------
38
+ "lille" => Restfully::Resource
39
+ "grenoble" => Restfully::Resource
40
+ "toulouse" => Restfully::Resource
41
+ "sophia" => Restfully::Resource
42
+ "rennes" => Restfully::Resource
43
+ "lyon" => Restfully::Resource
44
+ "nancy" => Restfully::Resource
45
+ "bordeaux" => Restfully::Resource
46
+ "orsay" => Restfully::Resource>
47
+ irb(main):008:0> root['version']
48
+ => "ee1f8111c8835496a9e4a6f7c9491c0238ee9827"
49
+ irb(main):009:0> root.version
50
+ => #<Restfully::Resource:0x8facf0
51
+ ------------ META ------------
52
+ @uri: "/grid5000/versions/ee1f8111c8835496a9e4a6f7c9491c0238ee9827"
53
+ @uid: "ee1f8111c8835496a9e4a6f7c9491c0238ee9827"
54
+ @type: "version"
55
+ @parent: Restfully::Resource
56
+ ------------ PROPERTIES ------------
57
+ "author" => "Cyril Constantin <>"
58
+ "date" => "Fri, 11 Sep 2009 14:32:48 GMT"
59
+ "message" => "[environments] update of environments on sites">
60
+
61
+ You may also prefer to use a configuration file to avoid entering the command line options:
62
+ $ echo '
63
+ base_uri: https://api.grid5000.fr/sid
64
+ relative_root_uri: /grid5000
65
+ username: MYLOGIN
66
+ password: MYPASSWORD
67
+ ' > ~/somewhere/api.grid5000.fr.yml
68
+
69
+ And then:
70
+ $ restfully -c ~/somewhere/api.grid5000.fr.yml
71
+
72
+ === As a library
73
+ See the +examples+ directory for examples.
6
74
 
7
75
  == Note on Patches/Pull Requests
8
76
 
data/Rakefile CHANGED
@@ -10,8 +10,8 @@ begin
10
10
  gem.email = "cyril.rohr@gmail.com"
11
11
  gem.homepage = "http://github.com/crohr/restfully"
12
12
  gem.authors = ["Cyril Rohr"]
13
- gem.add_dependency "rest-client"
14
-
13
+ gem.add_dependency "rest-client", '>= 1.0'
14
+ gem.rubyforge_project = 'restfully'
15
15
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
16
  end
17
17
 
@@ -31,16 +31,45 @@ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
31
  spec.rcov = true
32
32
  end
33
33
 
34
+ # These are new tasks
35
+ begin
36
+ require 'rake/contrib/sshpublisher'
37
+ namespace :rubyforge do
34
38
 
39
+ desc "Release gem and RDoc documentation to RubyForge"
40
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
35
41
 
42
+ namespace :release do
43
+ desc "Publish RDoc to RubyForge."
44
+ task :docs => [:rdoc] do
45
+ config = YAML.load(
46
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
47
+ )
36
48
 
37
- task :default => :spec
49
+ host = "#{config['username']}@rubyforge.org"
50
+ remote_dir = "/var/www/gforge-projects/the-perfect-gem/"
51
+ local_dir = 'rdoc'
38
52
 
39
- begin
40
- require 'yard'
41
- YARD::Rake::YardocTask.new
42
- rescue LoadError
43
- task :yardoc do
44
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
53
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
54
+ end
55
+ end
45
56
  end
57
+ rescue LoadError
58
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
46
59
  end
60
+
61
+ Jeweler::RubyforgeTasks.new do |rubyforge|
62
+ rubyforge.doc_task = "rdoc"
63
+ end
64
+
65
+
66
+ task :default => :spec
67
+
68
+ require 'rake/rdoctask'
69
+ Rake::RDocTask.new do |rdoc|
70
+ rdoc.rdoc_dir = 'rdoc'
71
+ rdoc.title = 'restfully'
72
+ rdoc.options << '--line-numbers' << '--inline-source'
73
+ rdoc.rdoc_files.include('README*')
74
+ rdoc.rdoc_files.include('lib/**/*.rb')
75
+ end
data/TODO.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ * Detects methods allowed on a resource using the Allow HTTP header, and generate corresponding wrappers.
2
+ * On a 406, detects accepted content based on a (custom) HTTP header, and retry (if possible) with another Accept header for which a parser is available.
3
+ * Investigate the possibility of using a :include => {} when loading resources or collection so that Restfully prefetches the included associations (using threads or events).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.1
data/bin/restfully CHANGED
@@ -10,7 +10,7 @@ require 'pp'
10
10
 
11
11
  @options = {}
12
12
  option_parser = OptionParser.new do |opts|
13
- opts.banner = "Usage: restfully base_url [root_uri] [options]"
13
+ opts.banner = "Usage: restfully base_uri [root_path] [options]"
14
14
 
15
15
  opts.on("-u=", "--username=", "Sets the username") do |u|
16
16
  @options['username'] = u
@@ -37,17 +37,17 @@ end
37
37
 
38
38
  option_parser.parse!
39
39
 
40
- @base_url = ARGV.shift
41
- @root_uri = ARGV.shift || "/"
40
+ @base_uri = ARGV.shift
41
+ @root_path = ARGV.shift || "/"
42
42
 
43
43
  if (config_filename = @options.delete('configuration_file')) && File.exists?(File.expand_path(config_filename))
44
44
  config = YAML.load_file(File.expand_path(config_filename))
45
- @base_url = config.delete('base_url') || @base_url
46
- @root_uri = config.delete('root_uri') || @root_uri
45
+ @base_uri = config.delete('base_uri') || @base_uri
46
+ @root_path = config.delete('root_path') || @root_path
47
47
  @options.merge!(config)
48
48
  end
49
49
 
50
- unless @base_url
50
+ unless @base_uri
51
51
  $stderr.puts option_parser.help
52
52
  exit(-1)
53
53
  else
@@ -63,10 +63,10 @@ else
63
63
  end
64
64
 
65
65
  def session
66
- @session ||= Restfully::Session.new(@base_url, @options.merge('root' => @root_uri, 'logger' => @logger))
66
+ @session ||= Restfully::Session.new(@base_uri, @options.merge('root_path' => @root_path, 'logger' => @logger))
67
67
  end
68
68
  def root
69
- @root ||= Restfully::Resource.new(session.root, session).load
69
+ @root ||= Restfully::Resource.new(session.root_path, session).load
70
70
  end
71
71
 
72
72
  root # preloads
data/examples/grid5000.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
3
  require 'pp'
3
4
 
4
5
  require File.dirname(__FILE__)+'/../lib/restfully'
@@ -9,7 +10,7 @@ logger.level = Logger::INFO
9
10
  # Restfully.adapter = Restfully::HTTP::RestClientAdapter
10
11
  # Restfully.adapter = Patron::Session
11
12
  RestClient.log = 'stdout'
12
- Restfully::Session.new('https://localhost:3443/sid', 'root' => '/grid5000', 'logger' => logger) do |grid, session|
13
+ Restfully::Session.new('https://localhost:3443/sid', 'root_path' => '/grid5000', 'logger' => logger) do |grid, session|
13
14
  grid_stats = {'hardware' => {}, 'system' => {}}
14
15
  grid.sites.each do |site_uid, site|
15
16
  site_stats = site.status.inject({'hardware' => {}, 'system' => {}}) {|accu, (node_uid, node_status)|
@@ -6,10 +6,11 @@ module Restfully
6
6
  attr_reader :state, :raw, :uri, :session, :title
7
7
 
8
8
  def initialize(uri, session, options = {})
9
+ options = options.symbolize_keys
9
10
  @uri = uri
10
- @title = options['title']
11
+ @title = options[:title]
11
12
  @session = session
12
- @raw = options['raw']
13
+ @raw = options[:raw]
13
14
  @state = :unloaded
14
15
  @items = {}
15
16
  super(@items)
@@ -19,25 +20,20 @@ module Restfully
19
20
 
20
21
  def load(options = {})
21
22
  options = options.symbolize_keys
22
- force_reload = options.delete(:reload) || false
23
- path = uri
24
- if options.has_key?(:query)
25
- path, query_string = uri.split("?")
26
- query_string ||= ""
27
- query_string.concat(options.delete(:query).to_params)
28
- path = "#{path}?#{query_string}"
29
- force_reload = true
30
- end
31
- if loaded? && force_reload == false
23
+ force_reload = !!options.delete(:reload) || options.has_key?(:query)
24
+ if loaded? && !force_reload
32
25
  self
33
- else
34
- @raw = session.get(path, options) if raw.nil? || force_reload
26
+ else
35
27
  @items.clear
28
+ if raw.nil? || force_reload
29
+ response = session.get(uri, options)
30
+ @raw = response.body
31
+ end
36
32
  raw.each do |key, value|
37
33
  next if key == 'links'
38
34
  self_link = (value['links'] || []).map{|link| Link.new(link)}.detect{|link| link.self?}
39
35
  if self_link && self_link.valid?
40
- @items.store(key, Resource.new(self_link.href, session, 'raw' => value).load)
36
+ @items.store(key, Resource.new(self_link.href, session, :raw => value).load)
41
37
  else
42
38
  session.logger.warn "Resource #{key} does not have a 'self' link. skipped."
43
39
  end
@@ -47,7 +43,7 @@ module Restfully
47
43
  end
48
44
  end
49
45
 
50
- def inspect(options = {:space => " "})
46
+ def inspect(options = {:space => "\t"})
51
47
  output = "#<#{self.class}:0x#{self.object_id.to_s(16)}"
52
48
  if loaded?
53
49
  output += "\n#{options[:space]}------------ META ------------"
@@ -0,0 +1,4 @@
1
+
2
+ module Restfully
3
+ class Error < StandardError; end
4
+ end
@@ -0,0 +1,30 @@
1
+ module Restfully
2
+ module HTTP
3
+ module Adapters
4
+
5
+ class AbstractAdapter
6
+ attr_reader :logger, :options
7
+ def initialize(base_uri, options = {})
8
+ @options = options.symbolize_keys
9
+ @logger = @options.delete(:logger) || Restfully::NullLogger.new
10
+ @base_uri = base_uri
11
+ logger.debug "base_uri = #{base_uri.inspect}, options = #{options.inspect}."
12
+ end
13
+
14
+ def get(request)
15
+ raise NotImplementedError, "GET is not supported by your adapter."
16
+ end
17
+ def post(request)
18
+ raise NotImplementedError, "POST is not supported by your adapter."
19
+ end
20
+ def put(request)
21
+ raise NotImplementedError, "PUT is not supported by your adapter."
22
+ end
23
+ def delete(request)
24
+ raise NotImplementedError, "DELETEis not supported by your adapter."
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'restfully/http/adapters/abstract_adapter'
2
+ require 'patron'
3
+
4
+ module Restfully
5
+ module HTTP
6
+ module Adapters
7
+ class PatronAdapter < AbstractAdapter
8
+
9
+ def initialize(base_url, options = {})
10
+ super(base_url, options)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ require 'restfully/http/adapters/abstract_adapter'
2
+ require 'restclient'
3
+
4
+ module Restfully
5
+ module HTTP
6
+ module Adapters
7
+ class RestClientAdapter < AbstractAdapter
8
+ def initialize(base_uri, options = {})
9
+ super(base_uri, options)
10
+ @options[:user] = @options.delete(:username)
11
+ end
12
+ def get(request)
13
+ begin
14
+ resource = RestClient::Resource.new(request.uri.to_s, @options)
15
+ response = resource.get(request.headers)
16
+ headers = response.headers
17
+ body = response.to_s
18
+ headers.delete(:status)
19
+ status = response.code
20
+ rescue RestClient::ExceptionWithResponse => e
21
+ body = e.http_body
22
+ headers = e.response.to_hash
23
+ status = e.http_code
24
+ end
25
+ Response.new(status, headers, body)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ module Restfully
2
+ module HTTP
3
+ class Error < Restfully::Error
4
+ attr_reader :response
5
+ def initialize(response)
6
+ @response = response
7
+ if response.body.kind_of?(Hash)
8
+ message = "#{response.status} #{response.body['title']}. #{response.body['message']}"
9
+ else
10
+ message = response.body
11
+ end
12
+ super(message)
13
+ end
14
+ end
15
+ class ClientError < Restfully::HTTP::Error; end
16
+ class ServerError < Restfully::HTTP::Error; end
17
+ end
18
+ end
19
+
20
+
@@ -0,0 +1,20 @@
1
+ module Restfully
2
+ module HTTP
3
+ module Headers
4
+ def sanitize_http_headers(headers = {})
5
+ sanitized_headers = {}
6
+ headers.each do |key, value|
7
+ sanitized_key = key.to_s.downcase.gsub(/[_-]/, ' ').split(' ').map{|word| word.capitalize}.join("-")
8
+ sanitized_value = case value
9
+ when Array
10
+ value.join(", ")
11
+ else
12
+ value
13
+ end
14
+ sanitized_headers[sanitized_key] = sanitized_value
15
+ end
16
+ sanitized_headers
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'uri'
2
+ module Restfully
3
+ module HTTP
4
+ class Request
5
+ include Headers
6
+ attr_reader :headers, :body, :uri
7
+ attr_accessor :retries
8
+ def initialize(url, options = {})
9
+ options = options.symbolize_keys
10
+ @uri = url.kind_of?(URI) ? url : URI.parse(url)
11
+ @headers = sanitize_http_headers(options.delete(:headers) || {})
12
+ if query = options.delete(:query)
13
+ @uri.query = [@uri.query, query.to_params].compact.join("&")
14
+ end
15
+ @body = body
16
+ @retries = 0
17
+ end
18
+
19
+ def add_headers(headers = {})
20
+ @headers.merge!(sanitize_http_headers(headers || {}))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Restfully
2
+ module HTTP
3
+ # Container for an HTTP Response. Has <tt>status</tt>, <tt>headers</tt> and <tt>body</tt> properties.
4
+ # The body is automatically parsed into a ruby object based on the response's <tt>Content-Type</tt> header.
5
+ class Response
6
+ include Headers, Restfully::Parsing
7
+ attr_reader :status, :headers
8
+ def initialize(status, headers, body)
9
+ @status = status.to_i
10
+ @headers = sanitize_http_headers(headers)
11
+ @body = (body.nil? || body.empty?) ? nil : body.to_s
12
+ end
13
+
14
+ def body
15
+ @unserialized_body ||= unserialize(@body, :content_type => @headers['Content-Type']) unless @body.nil?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ require 'restfully/http/headers'
2
+ require 'restfully/http/error'
3
+ require 'restfully/http/request'
4
+ require 'restfully/http/response'
5
+ require 'restfully/http/adapters/abstract_adapter'
6
+ module Restfully
7
+ module HTTP
8
+ end
9
+ end
@@ -2,23 +2,23 @@ require 'json'
2
2
 
3
3
  module Restfully
4
4
 
5
- class ParserNotFound < RestfullyError; end
6
-
7
5
  module Parsing
8
- def parse(object, options = {})
6
+
7
+ class ParserNotFound < Restfully::Error; end
8
+ def unserialize(object, options = {})
9
9
  content_type = options[:content_type]
10
- content_type ||= object.headers[:content_type] if object.respond_to?(:headers)
10
+ content_type ||= object.headers['Content-Type'] if object.respond_to?(:headers)
11
11
  case content_type
12
12
  when /^application\/json/i
13
13
  JSON.parse(object)
14
14
  else
15
- raise ParserNotFound, [object, content_type]
15
+ raise ParserNotFound.new("Content-Type '#{content_type}' is not supported. Cannot parse the given object.")
16
16
  end
17
17
  end
18
18
 
19
- def dump(object, options = {})
19
+ def serialize(object, options = {})
20
20
  content_type = options[:content_type]
21
- content_type ||= object.headers[:content_type] if object.respond_to?(:headers)
21
+ content_type ||= object.headers['Content-Type'] if object.respond_to?(:headers)
22
22
  case content_type
23
23
  when /^application\/json/i
24
24
  JSON.dump(object)
@@ -10,9 +10,10 @@ module Restfully
10
10
  attr_reader :uri, :session, :state, :raw, :uid, :associations, :type
11
11
 
12
12
  def initialize(uri, session, options = {})
13
+ options = options.symbolize_keys
13
14
  @uri = uri
14
15
  @session = session
15
- @raw = options['raw']
16
+ @raw = options[:raw]
16
17
  @state = :unloaded
17
18
  @attributes = {}
18
19
  super(@attributes)
@@ -32,21 +33,16 @@ module Restfully
32
33
 
33
34
  def load(options = {})
34
35
  options = options.symbolize_keys
35
- force_reload = options.delete(:reload) || false
36
- path = uri
37
- if options.has_key?(:query)
38
- path, query_string = uri.split("?")
39
- query_string ||= ""
40
- query_string.concat(options.delete(:query).to_params)
41
- path = "#{path}?#{query_string}"
42
- force_reload = true
43
- end
44
- if loaded? && force_reload == false
36
+ force_reload = !!options.delete(:reload) || options.has_key?(:query)
37
+ if loaded? && !force_reload
45
38
  self
46
- else
47
- @raw = session.get(path, options) if raw.nil? || force_reload
39
+ else
48
40
  @associations.clear
49
41
  @attributes.clear
42
+ if raw.nil? || force_reload
43
+ response = session.get(uri, options)
44
+ @raw = response.body
45
+ end
50
46
  (raw['links'] || []).each{|link| define_link(Link.new(link))}
51
47
  raw.each do |key, value|
52
48
  case key
@@ -73,7 +69,7 @@ module Restfully
73
69
  @associations.has_key?(method.to_s) || super(method, *args)
74
70
  end
75
71
 
76
- def inspect(options = {:space => " "})
72
+ def inspect(options = {:space => "\t"})
77
73
  output = "#<#{self.class}:0x#{self.object_id.to_s(16)}"
78
74
  if loaded?
79
75
  output += "\n#{options[:space]}------------ META ------------"
@@ -102,14 +98,15 @@ module Restfully
102
98
  when 'collection'
103
99
  raw_included = link.resolved? ? raw[link.title] : nil
104
100
  @associations[link.title] = Collection.new(link.href, session,
105
- 'raw' => raw_included,
106
- 'title' => link.title)
101
+ :raw => raw_included,
102
+ :title => link.title)
107
103
  when 'member'
108
104
  raw_included = link.resolved? ? raw[link.title] : nil
109
105
  @associations[link.title] = Resource.new(link.href, session,
110
- 'raw' => raw_included)
106
+ :title => link.title,
107
+ :raw => raw_included)
111
108
  when 'self'
112
- @uri = link.href
109
+ # we do nothing
113
110
  end
114
111
  else
115
112
  session.logger.warn link.errors.join("\n")
@@ -1,11 +1,8 @@
1
1
  require 'uri'
2
2
  require 'logger'
3
- require 'restclient'
4
3
  require 'restfully/parsing'
5
4
 
6
5
  module Restfully
7
- class Error < StandardError; end
8
- class HTTPError < Error; end
9
6
  class NullLogger
10
7
  def method_missing(method, *args)
11
8
  nil
@@ -13,49 +10,52 @@ module Restfully
13
10
  end
14
11
  class Session
15
12
  include Parsing
16
- attr_reader :base_url, :logger, :connection, :root
13
+ attr_reader :base_uri, :root_path, :logger, :connection, :root, :default_headers
17
14
 
18
15
  # TODO: use CacheableResource
19
- def initialize(base_url, options = {})
20
- options = options.stringify_keys
21
- @base_url = base_url
22
- @root = options['root'] || '/'
23
- @logger = options['logger'] || NullLogger.new
24
- @username = options['username']
25
- @password = options['password']
26
- # TODO: generalize
27
- # @connection = Patron::Session.new
28
- # @connection.timeout = 10
29
- # @connection.base_url = base_url
30
- # @connection.headers['User-Agent'] = 'restfully'
31
- # @connection.insecure = true
32
- # @connection.username = @user
33
- # @connection.password = @password
34
- @connection = RestClient::Resource.new(base_url || '', :user => @username, :password => @password)
35
- yield Resource.new(root, self).load, self if block_given?
16
+ def initialize(base_uri, options = {})
17
+ options = options.symbolize_keys
18
+ @base_uri = base_uri
19
+ @root_path = options.delete(:root_path) || '/'
20
+ @logger = options.delete(:logger) || NullLogger.new
21
+ @default_headers = options.delete(:default_headers) || {'Accept' => 'application/json'}
22
+ @connection = Restfully.adapter.new(@base_uri, options.merge(:logger => @logger))
23
+ @root = Resource.new(URI.parse(@root_path), self)
24
+ yield @root.load, self if block_given?
36
25
  end
37
26
 
38
- # TODO: uniformize headers hash (should accept symbols and strings in any capitalization format)
39
27
  # TODO: inspect response headers to determine which methods are available
40
- def get(path, headers = {})
41
- headers = headers.symbolize_keys
42
- headers[:accept] ||= 'application/json'
43
- logger.info "GET #{path} #{headers.inspect}"
44
- # TODO: should be able to choose HTTP lib to use, so we must provide abstract interface for HTTP handlers
45
- api = path.empty? ? connection : connection[path]
46
- response = api.get(headers)
47
- parse response#, :content_type => response.headers['Content-Type']
48
- # response = connection.get(path, headers)
49
- # logger.info response.headers
50
- # logger.info response.status
51
- # if (200..300).include?(response.status)
52
- # parse response.body, :content_type => response.headers['Content-Type']
53
- # else
54
- # TODO: better error management ;-)
55
- # raise HTTPError.new("Error: #{response.status}")
56
- # end
28
+ def get(path, options = {})
29
+ path = path.to_s
30
+ options = options.symbolize_keys
31
+ uri = URI.parse(base_uri)
32
+ path_uri = URI.parse(path)
33
+ # if the given path is complete URL, forget the base_uri, else append the path to the base_uri
34
+ unless path_uri.scheme.nil?
35
+ uri = path_uri
36
+ else
37
+ uri.path << path
38
+ end
39
+ request = HTTP::Request.new(uri, :headers => options.delete(:headers) || {}, :query => options.delete(:query) || {})
40
+ request.add_headers(@default_headers) unless @default_headers.empty?
41
+ logger.info "GET #{request.uri}, #{request.headers.inspect}"
42
+ response = connection.get(request)
43
+ logger.debug "Response to GET #{request.uri}: #{response.headers.inspect}"
44
+ response = deal_with_eventual_errors(response, request)
57
45
  end
58
-
59
- # TODO: add other HTTP methods
46
+
47
+ protected
48
+ def deal_with_eventual_errors(response, request)
49
+ case response.status
50
+ when 400..499
51
+ # should retry on 406 with another Accept header
52
+ raise Restfully::HTTP::ClientError, response
53
+ when 500..599
54
+ raise Restfully::HTTP::ServerError, response
55
+ else
56
+ response
57
+ end
58
+ end
59
+
60
60
  end
61
61
  end