connection.rb 5.2 KB
Newer Older
1
require 'active_support/core_ext/benchmark'
2 3 4 5 6 7
require 'net/https'
require 'date'
require 'time'
require 'uri'

module ActiveResource
8 9 10
  # Class to handle connections to remote web services.
  # This class is used by ActiveResource::Base to interface with REST
  # services.
11
  class Connection
12 13 14 15 16 17 18

    HTTP_FORMAT_HEADER_NAMES = {  :get => 'Accept',
      :put => 'Content-Type',
      :post => 'Content-Type',
      :delete => 'Accept'
    }

19
    attr_reader :site, :user, :password, :timeout
20
    attr_accessor :format
21

22 23 24 25 26 27
    class << self
      def requests
        @@requests ||= []
      end
    end

28 29
    # The +site+ parameter is required and will set the +site+
    # attribute to the URI for the remote resource service.
30
    def initialize(site, format = ActiveResource::Formats::XmlFormat)
31
      raise ArgumentError, 'Missing site URI' unless site
32
      @user = @password = nil
33
      self.site = site
34
      self.format = format
35
    end
36

37
    # Set URI for remote service.
38 39
    def site=(site)
      @site = site.is_a?(URI) ? site : URI.parse(site)
40 41
      @user = URI.decode(@site.user) if @site.user
      @password = URI.decode(@site.password) if @site.password
42 43
    end

P
Pratik Naik 已提交
44
    # Sets the user for remote service.
45 46 47 48
    def user=(user)
      @user = user
    end

P
Pratik Naik 已提交
49
    # Sets the password for remote service.
50 51
    def password=(password)
      @password = password
52
    end
53

P
Pratik Naik 已提交
54
    # Sets the number of seconds after which HTTP requests to the remote service should time out.
55 56 57 58
    def timeout=(timeout)
      @timeout = timeout
    end

P
Pratik Naik 已提交
59
    # Executes a GET request.
60
    # Used to get (find) resources.
61
    def get(path, headers = {})
62
      format.decode(request(:get, path, build_request_headers(headers, :get)).body)
63
    end
64

P
Pratik Naik 已提交
65
    # Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
66
    # Used to delete resources.
67
    def delete(path, headers = {})
68
      request(:delete, path, build_request_headers(headers, :delete))
69
    end
70

P
Pratik Naik 已提交
71
    # Executes a PUT request (see HTTP protocol documentation if unfamiliar).
72
    # Used to update resources.
73
    def put(path, body = '', headers = {})
74
      request(:put, path, body.to_s, build_request_headers(headers, :put))
75 76
    end

P
Pratik Naik 已提交
77
    # Executes a POST request.
78
    # Used to create new resources.
79
    def post(path, body = '', headers = {})
80
      request(:post, path, body.to_s, build_request_headers(headers, :post))
81
    end
82

P
Pratik Naik 已提交
83
    # Executes a HEAD request.
84 85
    # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
    def head(path, headers = {})
86 87 88
      request(:head, path, build_request_headers(headers))
    end

89

90
    private
P
Pratik Naik 已提交
91
      # Makes a request to the remote service.
92
      def request(method, path, *arguments)
93
        logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
94
        result = nil
J
Jeremy Kemper 已提交
95 96
        ms = Benchmark.ms { result = http.send(method, path, *arguments) }
        logger.info "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
97
        handle_response(result)
98 99
      rescue Timeout::Error => e
        raise TimeoutError.new(e.message)
100
      end
101

P
Pratik Naik 已提交
102
      # Handles response and error codes from the remote service.
103
      def handle_response(response)
104
        case response.code.to_i
105 106
          when 301,302
            raise(Redirection.new(response))
107
          when 200...400
108
            response
109 110 111 112 113 114
          when 400
            raise(BadRequest.new(response))
          when 401
            raise(UnauthorizedAccess.new(response))
          when 403
            raise(ForbiddenAccess.new(response))
115 116
          when 404
            raise(ResourceNotFound.new(response))
117 118
          when 405
            raise(MethodNotAllowed.new(response))
119 120
          when 409
            raise(ResourceConflict.new(response))
121 122
          when 422
            raise(ResourceInvalid.new(response))
123
          when 401...500
124 125 126 127 128 129 130 131
            raise(ClientError.new(response))
          when 500...600
            raise(ServerError.new(response))
          else
            raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
        end
      end

P
Pratik Naik 已提交
132
      # Creates new Net::HTTP instance for communication with the
133
      # remote service and resources.
134
      def http
135 136
        http             = Net::HTTP.new(@site.host, @site.port)
        http.use_ssl     = @site.is_a?(URI::HTTPS)
137
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
138
        http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
139
        http
140
      end
141 142

      def default_header
143
        @default_header ||= {}
144
      end
145

146
      # Builds headers for request to remote service.
147
      def build_request_headers(headers, http_method=nil)
E
Edgar J. Suarez 已提交
148
        authorization_header.update(default_header).update(http_format_header(http_method)).update(headers)
149
      end
150

151
      # Sets authorization header
152
      def authorization_header
153
        (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
154
      end
155

156 157 158 159
      def http_format_header(http_method)
        {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
      end

160
      def logger #:nodoc:
161
        Base.logger
162
      end
163
  end
164
end