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/.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)
|