httpi 1.1.1 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/httpi.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  require "logger"
2
+
2
3
  require "httpi/version"
3
4
  require "httpi/request"
4
- require "httpi/adapter"
5
+
6
+ require "httpi/adapter/httpclient"
7
+ require "httpi/adapter/curb"
8
+ require "httpi/adapter/net_http"
9
+ require "httpi/adapter/em_http"
5
10
 
6
11
  # = HTTPI
7
12
  #
@@ -12,12 +17,12 @@ require "httpi/adapter"
12
17
  #
13
18
  # == GET
14
19
  #
15
- # request = HTTPI::Request.new :url => "http://example.com"
16
- # HTTPI.get request, :httpclient
20
+ # request = HTTPI::Request.new("http://example.com")
21
+ # HTTPI.get(request, :httpclient)
17
22
  #
18
23
  # === Shortcuts
19
24
  #
20
- # HTTPI.get "http://example.com", :curb
25
+ # HTTPI.get("http://example.com", :curb)
21
26
  #
22
27
  # == POST
23
28
  #
@@ -25,20 +30,20 @@ require "httpi/adapter"
25
30
  # request.url = "http://example.com"
26
31
  # request.body = "<some>xml</some>"
27
32
  #
28
- # HTTPI.post request, :httpclient
33
+ # HTTPI.post(request, :httpclient)
29
34
  #
30
35
  # === Shortcuts
31
36
  #
32
- # HTTPI.post "http://example.com", "<some>xml</some>", :curb
37
+ # HTTPI.post("http://example.com", "<some>xml</some>", :curb)
33
38
  #
34
39
  # == HEAD
35
40
  #
36
- # request = HTTPI::Request.new :url => "http://example.com"
37
- # HTTPI.head request, :httpclient
41
+ # request = HTTPI::Request.new("http://example.com")
42
+ # HTTPI.head(request, :httpclient)
38
43
  #
39
44
  # === Shortcuts
40
45
  #
41
- # HTTPI.head "http://example.com", :curb
46
+ # HTTPI.head("http://example.com", :curb)
42
47
  #
43
48
  # == PUT
44
49
  #
@@ -46,20 +51,20 @@ require "httpi/adapter"
46
51
  # request.url = "http://example.com"
47
52
  # request.body = "<some>xml</some>"
48
53
  #
49
- # HTTPI.put request, :httpclient
54
+ # HTTPI.put(request, :httpclient)
50
55
  #
51
56
  # === Shortcuts
52
57
  #
53
- # HTTPI.put "http://example.com", "<some>xml</some>", :curb
58
+ # HTTPI.put("http://example.com", "<some>xml</some>", :curb)
54
59
  #
55
60
  # == DELETE
56
61
  #
57
- # request = HTTPI::Request.new :url => "http://example.com"
58
- # HTTPI.delete request, :httpclient
62
+ # request = HTTPI::Request.new("http://example.com")
63
+ # HTTPI.delete(request, :httpclient)
59
64
  #
60
65
  # === Shortcuts
61
66
  #
62
- # HTTPI.delete "http://example.com", :curb
67
+ # HTTPI.delete("http://example.com", :curb)
63
68
  #
64
69
  # == More control
65
70
  #
@@ -73,64 +78,61 @@ module HTTPI
73
78
 
74
79
  REQUEST_METHODS = [:get, :post, :head, :put, :delete]
75
80
 
76
- DEFAULT_LOG_LEVEL = :warn
81
+ DEFAULT_LOG_LEVEL = :debug
82
+
83
+ class Error < StandardError; end
84
+ class TimeoutError < Error; end
85
+ class NotSupportedError < Error; end
86
+ class NotImplementedError < Error; end
87
+
88
+ class SSLError < Error
89
+ def initialize(message = nil, original = $!)
90
+ super(message)
91
+ @original = original
92
+ end
93
+ attr_reader :original
94
+ end
77
95
 
78
96
  class << self
79
97
 
80
98
  # Executes an HTTP GET request.
81
99
  def get(request, adapter = nil)
82
- request = Request.new :url => request if request.kind_of? String
83
-
84
- with_adapter :get, request, adapter do |adapter|
85
- yield adapter.client if block_given?
86
- adapter.get request
87
- end
100
+ request = Request.new(request) if request.kind_of? String
101
+ request(:get, request, adapter)
88
102
  end
89
103
 
90
104
  # Executes an HTTP POST request.
91
105
  def post(*args)
92
106
  request, adapter = request_and_adapter_from(args)
93
-
94
- with_adapter :post, request, adapter do |adapter|
95
- yield adapter.client if block_given?
96
- adapter.post request
97
- end
107
+ request(:post, request, adapter)
98
108
  end
99
109
 
100
110
  # Executes an HTTP HEAD request.
101
111
  def head(request, adapter = nil)
102
- request = Request.new :url => request if request.kind_of? String
103
-
104
- with_adapter :head, request, adapter do |adapter|
105
- yield adapter.client if block_given?
106
- adapter.head request
107
- end
112
+ request = Request.new(request) if request.kind_of? String
113
+ request(:head, request, adapter)
108
114
  end
109
115
 
110
116
  # Executes an HTTP PUT request.
111
117
  def put(*args)
112
118
  request, adapter = request_and_adapter_from(args)
113
-
114
- with_adapter :put, request, adapter do |adapter|
115
- yield adapter.client if block_given?
116
- adapter.put request
117
- end
119
+ request(:put, request, adapter)
118
120
  end
119
121
 
120
122
  # Executes an HTTP DELETE request.
121
123
  def delete(request, adapter = nil)
122
- request = Request.new :url => request if request.kind_of? String
123
-
124
- with_adapter :delete, request, adapter do |adapter|
125
- yield adapter.client if block_given?
126
- adapter.delete request
127
- end
124
+ request = Request.new(request) if request.kind_of? String
125
+ request(:delete, request, adapter)
128
126
  end
129
127
 
130
128
  # Executes an HTTP request for the given +method+.
131
129
  def request(method, request, adapter = nil)
132
- raise ArgumentError, "Invalid request method: #{method}" unless REQUEST_METHODS.include? method
133
- send method, request, adapter
130
+ adapter_class = load_adapter(adapter, request)
131
+
132
+ yield adapter_class.client if block_given?
133
+ log_request(method, request, Adapter.identify(adapter_class.class))
134
+
135
+ adapter_class.request(method)
134
136
  end
135
137
 
136
138
  # Shortcut for setting the default adapter to use.
@@ -151,7 +153,7 @@ module HTTPI
151
153
 
152
154
  # Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
153
155
  def logger
154
- @logger ||= ::Logger.new STDOUT
156
+ @logger ||= ::Logger.new($stdout)
155
157
  end
156
158
 
157
159
  # Sets the log level.
@@ -174,23 +176,19 @@ module HTTPI
174
176
  @log_level = nil
175
177
  end
176
178
 
177
- private
179
+ private
178
180
 
179
- # Checks whether +args+ contains of an <tt>HTTPI::Request</tt> or a URL
180
- # and a request body plus an optional adapter and returns an Array with
181
- # an <tt>HTTPI::Request</tt> and (if given) an adapter.
182
181
  def request_and_adapter_from(args)
183
182
  return args if args[0].kind_of? Request
184
183
  [Request.new(:url => args[0], :body => args[1]), args[2]]
185
184
  end
186
185
 
187
- # Expects a request +method+, a +request+ and an +adapter+ (defaults to
188
- # <tt>Adapter.use</tt>) and yields an instance of the adapter to a given block.
189
- def with_adapter(method, request, adapter)
190
- adapter, adapter_class = Adapter.load adapter
186
+ def load_adapter(adapter, request)
187
+ Adapter.load(adapter).new(request)
188
+ end
191
189
 
192
- log "HTTPI executes HTTP #{method.to_s.upcase} using the #{adapter} adapter"
193
- yield adapter_class.new(request)
190
+ def log_request(method, request, adapter)
191
+ log("HTTPI #{method.to_s.upcase} request to #{request.url.host} (#{adapter})")
194
192
  end
195
193
 
196
194
  end
data/lib/httpi/adapter.rb CHANGED
@@ -1,7 +1,3 @@
1
- require "httpi/adapter/httpclient"
2
- require "httpi/adapter/curb"
3
- require "httpi/adapter/net_http"
4
-
5
1
  module HTTPI
6
2
 
7
3
  # = HTTPI::Adapter
@@ -10,19 +6,22 @@ module HTTPI
10
6
  #
11
7
  # * httpclient
12
8
  # * curb
9
+ # * em_http
13
10
  # * net/http
14
11
  module Adapter
15
12
 
16
- ADAPTERS = {
17
- :httpclient => { :class => HTTPClient, :require => "httpclient" },
18
- :curb => { :class => Curb, :require => "curb" },
19
- :net_http => { :class => NetHTTP, :require => "net/https" }
20
- }
13
+ ADAPTERS = {}
14
+ ADAPTER_CLASS_MAP = {}
21
15
 
22
- LOAD_ORDER = [:httpclient, :curb, :net_http]
16
+ LOAD_ORDER = [:httpclient, :curb, :em_http, :net_http]
23
17
 
24
18
  class << self
25
19
 
20
+ def register(name, adapter_class, deps)
21
+ ADAPTERS[name] = { :class => adapter_class, :deps => deps }
22
+ ADAPTER_CLASS_MAP[adapter_class] = name
23
+ end
24
+
26
25
  def use=(adapter)
27
26
  return @adapter = nil if adapter.nil?
28
27
 
@@ -35,16 +34,28 @@ module HTTPI
35
34
  @adapter ||= default_adapter
36
35
  end
37
36
 
37
+ def identify(adapter_class)
38
+ ADAPTER_CLASS_MAP[adapter_class]
39
+ end
40
+
38
41
  def load(adapter)
39
- adapter = adapter ? validate_adapter!(adapter) : use
40
- [adapter, ADAPTERS[adapter][:class]]
42
+ adapter ||= use
43
+
44
+ validate_adapter!(adapter)
45
+ load_adapter(adapter)
46
+ ADAPTERS[adapter][:class]
41
47
  end
42
48
 
43
- private
49
+ def load_adapter(adapter)
50
+ ADAPTERS[adapter][:deps].each do |dep|
51
+ require dep
52
+ end
53
+ end
54
+
55
+ private
44
56
 
45
57
  def validate_adapter!(adapter)
46
58
  raise ArgumentError, "Invalid HTTPI adapter: #{adapter}" unless ADAPTERS[adapter]
47
- adapter
48
59
  end
49
60
 
50
61
  def default_adapter
@@ -58,10 +69,6 @@ module HTTPI
58
69
  end
59
70
  end
60
71
 
61
- def load_adapter(adapter)
62
- require ADAPTERS[adapter][:require]
63
- end
64
-
65
72
  end
66
73
  end
67
74
  end
@@ -0,0 +1,35 @@
1
+ require "httpi"
2
+ require "httpi/adapter"
3
+
4
+ module HTTPI
5
+ module Adapter
6
+
7
+ # HTTPI::Adapter::Base
8
+ #
9
+ # Allows you to build your own adapter by implementing all public instance methods.
10
+ # Register your adapter by calling the base class' .register method.
11
+ class Base
12
+
13
+ # Registers an adapter.
14
+ def self.register(name, options = {})
15
+ deps = options.fetch(:deps, [])
16
+ Adapter.register(name, self, deps)
17
+ end
18
+
19
+ def initialize(request)
20
+ end
21
+
22
+ # Returns a client instance.
23
+ def client
24
+ raise NotImplementedError, "Adapters need to implement a #client method"
25
+ end
26
+
27
+ # Executes arbitrary HTTP requests.
28
+ # @see HTTPI.request
29
+ def request(method)
30
+ raise NotImplementedError, "Adapters need to implement a #request method"
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,4 @@
1
+ require "httpi/adapter/base"
1
2
  require "httpi/response"
2
3
 
3
4
  module HTTPI
@@ -7,89 +8,87 @@ module HTTPI
7
8
  #
8
9
  # Adapter for the Curb client.
9
10
  # http://rubygems.org/gems/curb
10
- class Curb
11
+ class Curb < Base
11
12
 
12
- def initialize(request = nil)
13
- end
14
-
15
- # Returns a memoized <tt>Curl::Easy</tt> instance.
16
- def client
17
- @client ||= Curl::Easy.new
18
- end
13
+ register :curb, :deps => %w(curb)
19
14
 
20
- # Executes an HTTP GET request.
21
- # @see HTTPI.get
22
- def get(request)
23
- do_request(request) { |client| client.http_get }
15
+ def initialize(request)
16
+ @request = request
17
+ @client = Curl::Easy.new
24
18
  end
25
19
 
26
- # Executes an HTTP POST request.
27
- # @see HTTPI.post
28
- def post(request)
29
- do_request(request) { |client| client.http_post request.body }
30
- end
20
+ attr_reader :client
31
21
 
32
- # Executes an HTTP HEAD request.
33
- # @see HTTPI.head
34
- def head(request)
35
- do_request(request) { |client| client.http_head }
36
- end
22
+ def request(method)
23
+ unless REQUEST_METHODS.include? method
24
+ raise NotSupportedError, "Curb does not support custom HTTP methods"
25
+ end
37
26
 
38
- # Executes an HTTP PUT request.
39
- # @see HTTPI.put
40
- def put(request)
41
- do_request(request) { |client| client.http_put request.body }
42
- end
27
+ arguments = ["http_#{method}"]
28
+ if [:put, :post].include? method
29
+ arguments << (@request.body || "")
30
+ end
43
31
 
44
- # Executes an HTTP DELETE request.
45
- # @see HTTPI.delete
46
- def delete(request)
47
- do_request(request) { |client| client.http_delete }
32
+ do_request { |client| client.send(*arguments) }
33
+ rescue Curl::Err::SSLCACertificateError
34
+ raise SSLError
35
+ rescue Curl::Err::SSLPeerCertificateError
36
+ raise SSLError
48
37
  end
49
38
 
50
- private
39
+ private
51
40
 
52
- def do_request(request)
53
- setup_client request
54
- yield client
55
- respond_with client
41
+ def do_request
42
+ setup_client
43
+ yield @client
44
+ respond_with @client
56
45
  end
57
46
 
58
- def setup_client(request)
59
- basic_setup request
60
- setup_http_auth request if request.auth.http?
61
- setup_gssnegotiate_auth request if request.auth.gssnegotiate?
62
- setup_ssl_auth request.auth.ssl if request.auth.ssl?
47
+ def setup_client
48
+ basic_setup
49
+ setup_http_auth if @request.auth.http?
50
+ setup_gssnegotiate_auth if @request.auth.gssnegotiate?
51
+ setup_ssl_auth if @request.auth.ssl?
63
52
  end
64
53
 
65
- def basic_setup(request)
66
- client.url = request.url.to_s
67
- client.proxy_url = request.proxy.to_s if request.proxy
68
- client.timeout = request.read_timeout if request.read_timeout
69
- client.connect_timeout = request.open_timeout if request.open_timeout
70
- client.headers = request.headers.to_hash
71
- client.verbose = false
54
+ def basic_setup
55
+ @client.url = @request.url.to_s
56
+ @client.proxy_url = @request.proxy.to_s if @request.proxy
57
+ @client.timeout = @request.read_timeout if @request.read_timeout
58
+ @client.connect_timeout = @request.open_timeout if @request.open_timeout
59
+ @client.headers = @request.headers.to_hash
60
+ @client.verbose = false
72
61
  end
73
62
 
74
- def setup_http_auth(request)
75
- client.http_auth_types = request.auth.type
76
- client.username, client.password = *request.auth.credentials
63
+ def setup_http_auth
64
+ @client.http_auth_types = @request.auth.type
65
+ @client.username, @client.password = *@request.auth.credentials
77
66
  end
78
67
 
79
- def setup_gssnegotiate_auth(request)
80
- client.http_auth_types = request.auth.type
68
+ def setup_gssnegotiate_auth
69
+ @client.http_auth_types = @request.auth.type
81
70
  # The curl man page (http://curl.haxx.se/docs/manpage.html) says that
82
71
  # you have to specify a fake username when using Negotiate auth, and
83
72
  # they use ':' in their example.
84
- client.username = ':'
73
+ @client.username = ':'
85
74
  end
86
75
 
87
- def setup_ssl_auth(ssl)
88
- client.cert_key = ssl.cert_key_file
89
- client.cert = ssl.cert_file
90
- client.cacert = ssl.ca_cert_file if ssl.ca_cert_file
91
- client.certtype = ssl.cert_type.to_s.upcase
92
- client.ssl_verify_peer = ssl.verify_mode == :peer
76
+ def setup_ssl_auth
77
+ ssl = @request.auth.ssl
78
+
79
+ unless ssl.verify_mode == :none
80
+ @client.cert_key = ssl.cert_key_file
81
+ @client.cert = ssl.cert_file
82
+ @client.cacert = ssl.ca_cert_file if ssl.ca_cert_file
83
+ @client.certtype = ssl.cert_type.to_s.upcase
84
+ end
85
+
86
+ @client.ssl_verify_peer = ssl.verify_mode == :peer
87
+ @client.ssl_version = case ssl.ssl_version
88
+ when :TLSv1 then 1
89
+ when :SSLv2 then 2
90
+ when :SSLv3 then 3
91
+ end
93
92
  end
94
93
 
95
94
  def respond_with(client)