httpi 1.1.1 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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)