| 1 | | require 'net/http' |
| 2 | | require 'net/https' |
| 3 | | require 'uri' |
| 4 | | require 'cgi' |
| 5 | | |
| 6 | | begin |
| 7 | | require 'xml_simple' |
| 8 | | rescue LoadError |
| 9 | | require 'rubygems' |
| 10 | | require 'active_support' |
| 11 | | begin |
| 12 | | require 'xml_simple' |
| 13 | | rescue LoadError |
| 14 | | require 'xmlsimple' |
| 15 | | end |
| 16 | | end |
| 17 | | |
| 18 | | |
| 19 | | # A very simple REST client, best explained by example: |
| 20 | | # |
| 21 | | # # Retrieve a Kitten and print its name and colour |
| 22 | | # kitten = Restr.get('http://example.com/kittens/1.xml') |
| 23 | | # puts kitten['name'] |
| 24 | | # puts kitten['colour'] |
| 25 | | # |
| 26 | | # # Create a Kitten |
| 27 | | # kitten = Restr.post('http://example.com/kittens.xml', |
| 28 | | # :name => 'batman', :colour => 'black') |
| 29 | | # |
| 30 | | # # Update a Kitten |
| 31 | | # kitten = Restr.put('http://example.com/kittens/1.xml', |
| 32 | | # :age => '6 months') |
| 33 | | # |
| 34 | | # # Delete a Kitten :( |
| 35 | | # kitten = Restr.delete('http://example.com/kittens/1.xml') |
| 36 | | # |
| 37 | | # # Retrieve a list of Kittens |
| 38 | | # kittens = Restr.get('http://example.com/kittens.xml') |
| 39 | | # |
| 40 | | # When the response to a Restr request has content type 'text/xml', the |
| 41 | | # response body will be parsed from XML into a nested Hash (using XmlSimple |
| 42 | | # -- see http://xml-simple.rubyforge.org/). Otherwise the response is |
| 43 | | # returned untouched, as a String. |
| 44 | | # |
| 45 | | # If the remote REST resource requires authentication (Restr only supports |
| 46 | | # HTTP Basic authentication, for now): |
| 47 | | # |
| 48 | | # Restr.get('http://example.com/kittens/1.xml, {}, |
| 49 | | # {:username => 'foo', :password => 'bar'}) |
| 50 | | # |
| 51 | | class Restr |
| 52 | | @@log = nil |
| 53 | | |
| 54 | | def self.logger=(logger) |
| 55 | | @@log = logger.dup |
| 56 | | # ActiveSupport's BufferedLogger doesn't seem to support progname= :( |
| 57 | | @@log.progname = self.name if @@log.respond_to?(:progname) |
| 58 | | end |
| 59 | | |
| 60 | | def self.method_missing(method, *args) |
| 61 | | self.do(method, args[0], args[1] || {}, args[2]) |
| 62 | | end |
| 63 | | |
| 64 | | def self.do(method, url, params = {}, auth = nil) |
| 65 | | uri = URI.parse(url) |
| 66 | | params = {} unless params |
| 67 | | |
| 68 | | method_mod = method.to_s.downcase.capitalize |
| 69 | | unless Net::HTTP.const_defined?(method_mod) |
| 70 | | raise InvalidRequestMethod, |
| 71 | | "Callback method #{method.inspect} is not a valid HTTP request method." |
| 72 | | end |
| 73 | | |
| 74 | | if method_mod == 'Get' |
| 75 | | q = params.collect{|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join("&") |
| 76 | | if uri.query |
| 77 | | uri.query += "&#{q}" |
| 78 | | else |
| 79 | | uri.query = q |
| 80 | | end |
| 81 | | end |
| 82 | | |
| 83 | | req = Net::HTTP.const_get(method_mod).new(uri.request_uri) |
| 84 | | |
| 85 | | |
| 86 | | if auth |
| 87 | | raise ArgumentError, |
| 88 | | "The `auth` parameter must be a Hash with a :username and :password value." unless |
| 89 | | auth.kind_of? Hash |
| 90 | | req.basic_auth auth[:username] || auth['username'], auth[:password] || auth['password'] |
| 91 | | end |
| 92 | | |
| 93 | | unless method_mod == 'Get' |
| 94 | | req.set_form_data(params, '&') |
| 95 | | end |
| 96 | | |
| 97 | | @@log.debug("Sending #{method.inspect} request to #{url.inspect} with data #{params.inspect}"+ |
| 98 | | (auth ? " with authentication" : "")+".") if @@log |
| 99 | | |
| 100 | | client = Net::HTTP.new(uri.host, uri.port) |
| 101 | | client.use_ssl = (uri.scheme == 'https') |
| 102 | | res = client.start do |http| |
| 103 | | http.request(req) |
| 104 | | end |
| 105 | | |
| 106 | | case res |
| 107 | | when Net::HTTPSuccess |
| 108 | | if res.content_type == 'text/xml' |
| 109 | | @@log.debug("Got XML response.") if @@log |
| 110 | | return XmlSimple.xml_in_string(res.body, |
| 111 | | 'forcearray' => false, |
| 112 | | 'keeproot' => false |
| 113 | | ) |
| 114 | | else |
| 115 | | @@log.debug("Got #{res.content_type.inspect} response.") if @@log |
| 116 | | return res.body |
| 117 | | end |
| 118 | | else |
| 119 | | $LAST_ERROR_BODY = res.body # FIXME: this is dumb... need a better way of reporting errors |
| 120 | | @@log.error("Got error response '#{res.message}(#{res.code})': #{$LAST_ERROR_BODY}") if @@log |
| 121 | | res.error! |
| 122 | | end |
| 123 | | end |
| 124 | | |
| 125 | | class InvalidRequestMethod < Exception |
| 126 | | end |
| 127 | | end |