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/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +51 -26
- data/Gemfile +6 -4
- data/README.md +19 -205
- data/httpi.gemspec +7 -10
- data/lib/httpi.rb +54 -56
- data/lib/httpi/adapter.rb +25 -18
- data/lib/httpi/adapter/base.rb +35 -0
- data/lib/httpi/adapter/curb.rb +59 -60
- data/lib/httpi/adapter/em_http.rb +126 -0
- data/lib/httpi/adapter/httpclient.rb +33 -63
- data/lib/httpi/adapter/net_http.rb +44 -62
- data/lib/httpi/auth/ssl.rb +26 -2
- data/lib/httpi/dime.rb +45 -29
- data/lib/httpi/request.rb +1 -1
- data/lib/httpi/response.rb +6 -4
- data/lib/httpi/version.rb +1 -1
- data/spec/httpi/adapter/base_spec.rb +23 -0
- data/spec/httpi/adapter/curb_spec.rb +107 -67
- data/spec/httpi/adapter/em_http_spec.rb +168 -0
- data/spec/httpi/adapter/httpclient_spec.rb +67 -56
- data/spec/httpi/adapter/net_http_spec.rb +62 -47
- data/spec/httpi/adapter_spec.rb +15 -2
- data/spec/httpi/auth/ssl_spec.rb +34 -1
- data/spec/httpi/httpi_spec.rb +80 -115
- data/spec/integration/fixtures/ca.pem +23 -0
- data/spec/integration/fixtures/ca_all.pem +44 -0
- data/spec/integration/fixtures/htdigest +1 -0
- data/spec/integration/fixtures/htpasswd +2 -0
- data/spec/integration/fixtures/server.cert +19 -0
- data/spec/integration/fixtures/server.key +15 -0
- data/spec/integration/fixtures/subca.pem +21 -0
- data/spec/integration/request_spec.rb +15 -2
- data/spec/integration/ssl_server.rb +70 -0
- data/spec/integration/ssl_spec.rb +102 -0
- data/spec/support/fixture.rb +1 -1
- metadata +60 -73
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
|
-
|
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
|
16
|
-
# HTTPI.get
|
20
|
+
# request = HTTPI::Request.new("http://example.com")
|
21
|
+
# HTTPI.get(request, :httpclient)
|
17
22
|
#
|
18
23
|
# === Shortcuts
|
19
24
|
#
|
20
|
-
# HTTPI.get
|
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
|
33
|
+
# HTTPI.post(request, :httpclient)
|
29
34
|
#
|
30
35
|
# === Shortcuts
|
31
36
|
#
|
32
|
-
# HTTPI.post
|
37
|
+
# HTTPI.post("http://example.com", "<some>xml</some>", :curb)
|
33
38
|
#
|
34
39
|
# == HEAD
|
35
40
|
#
|
36
|
-
# request = HTTPI::Request.new
|
37
|
-
# HTTPI.head
|
41
|
+
# request = HTTPI::Request.new("http://example.com")
|
42
|
+
# HTTPI.head(request, :httpclient)
|
38
43
|
#
|
39
44
|
# === Shortcuts
|
40
45
|
#
|
41
|
-
# HTTPI.head
|
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
|
54
|
+
# HTTPI.put(request, :httpclient)
|
50
55
|
#
|
51
56
|
# === Shortcuts
|
52
57
|
#
|
53
|
-
# HTTPI.put
|
58
|
+
# HTTPI.put("http://example.com", "<some>xml</some>", :curb)
|
54
59
|
#
|
55
60
|
# == DELETE
|
56
61
|
#
|
57
|
-
# request = HTTPI::Request.new
|
58
|
-
# HTTPI.delete
|
62
|
+
# request = HTTPI::Request.new("http://example.com")
|
63
|
+
# HTTPI.delete(request, :httpclient)
|
59
64
|
#
|
60
65
|
# === Shortcuts
|
61
66
|
#
|
62
|
-
# HTTPI.delete
|
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 = :
|
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
|
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
|
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
|
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
|
-
|
133
|
-
|
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
|
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
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
adapter, adapter_class = Adapter.load adapter
|
186
|
+
def load_adapter(adapter, request)
|
187
|
+
Adapter.load(adapter).new(request)
|
188
|
+
end
|
191
189
|
|
192
|
-
|
193
|
-
|
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
|
-
|
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
|
40
|
-
|
42
|
+
adapter ||= use
|
43
|
+
|
44
|
+
validate_adapter!(adapter)
|
45
|
+
load_adapter(adapter)
|
46
|
+
ADAPTERS[adapter][:class]
|
41
47
|
end
|
42
48
|
|
43
|
-
|
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
|
data/lib/httpi/adapter/curb.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
27
|
+
arguments = ["http_#{method}"]
|
28
|
+
if [:put, :post].include? method
|
29
|
+
arguments << (@request.body || "")
|
30
|
+
end
|
43
31
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
39
|
+
private
|
51
40
|
|
52
|
-
def do_request
|
53
|
-
setup_client
|
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
|
59
|
-
basic_setup
|
60
|
-
setup_http_auth
|
61
|
-
setup_gssnegotiate_auth
|
62
|
-
setup_ssl_auth
|
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
|
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
|
75
|
-
client.http_auth_types = request.auth.type
|
76
|
-
client.username, client.password =
|
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
|
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
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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)
|