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
@@ -0,0 +1,126 @@
|
|
1
|
+
require "httpi/adapter/base"
|
2
|
+
require "httpi/response"
|
3
|
+
|
4
|
+
module HTTPI
|
5
|
+
module Adapter
|
6
|
+
|
7
|
+
# An HTTPI adapter for `EventMachine::HttpRequest`. Due to limitations of
|
8
|
+
# the em-httprequest library, not all features are supported. In particular,
|
9
|
+
#
|
10
|
+
# * CA files,
|
11
|
+
# * certificate verification modes other than "none" and "peer,"
|
12
|
+
# * NTLM authentication,
|
13
|
+
# * digest authentication, and
|
14
|
+
# * password-protected certificate keys
|
15
|
+
#
|
16
|
+
# are supported by HTTPI but not em-httprequest.
|
17
|
+
#
|
18
|
+
# In addition, some features of em-httprequest are not represented in HTTPI
|
19
|
+
# and are therefore not supported. In particular,
|
20
|
+
#
|
21
|
+
# * SOCKS5 proxying,
|
22
|
+
# * automatic redirect following,
|
23
|
+
# * response streaming,
|
24
|
+
# * file body streaming,
|
25
|
+
# * keepalive,
|
26
|
+
# * pipelining, and
|
27
|
+
# * multi-request
|
28
|
+
#
|
29
|
+
# are supported by em-httprequest but not HTTPI.
|
30
|
+
class EmHttpRequest < Base
|
31
|
+
|
32
|
+
register :em_http, :deps => %w(em-synchrony em-synchrony/em-http em-http)
|
33
|
+
|
34
|
+
def initialize(request)
|
35
|
+
@request = request
|
36
|
+
@client = EventMachine::HttpRequest.new build_request_url(request.url)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :client
|
40
|
+
|
41
|
+
def cert_directory
|
42
|
+
@cert_directory ||= "/tmp"
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_writer :cert_directory
|
46
|
+
|
47
|
+
# Executes arbitrary HTTP requests.
|
48
|
+
# @see HTTPI.request
|
49
|
+
def request(method)
|
50
|
+
_request { |options| @client.send method, options }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def _request
|
56
|
+
options = client_options
|
57
|
+
setup_proxy(options) if @request.proxy
|
58
|
+
setup_http_auth(options) if @request.auth.http?
|
59
|
+
|
60
|
+
if @request.auth.ssl?
|
61
|
+
raise NotSupportedError, "EM-HTTP-Request does not support SSL client auth"
|
62
|
+
end
|
63
|
+
|
64
|
+
start_time = Time.now
|
65
|
+
respond_with yield(options), start_time
|
66
|
+
end
|
67
|
+
|
68
|
+
def client_options
|
69
|
+
{
|
70
|
+
:query => @request.url.query,
|
71
|
+
:connect_timeout => @request.open_timeout,
|
72
|
+
:inactivity_timeout => @request.read_timeout,
|
73
|
+
:head => @request.headers.to_hash,
|
74
|
+
:body => @request.body
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def setup_proxy(options)
|
79
|
+
options[:proxy] = {
|
80
|
+
:host => @request.proxy.host,
|
81
|
+
:port => @request.proxy.port,
|
82
|
+
:authorization => [@request.proxy.user, @request.proxy.password]
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def setup_http_auth(options)
|
87
|
+
unless @request.auth.type == :basic
|
88
|
+
raise NotSupportedError, "EM-HTTP-Request does only support HTTP basic auth"
|
89
|
+
end
|
90
|
+
|
91
|
+
options[:head] ||= {}
|
92
|
+
options[:head][:authorization] = @request.auth.credentials
|
93
|
+
end
|
94
|
+
|
95
|
+
def respond_with(http, start_time)
|
96
|
+
raise TimeoutError, "EM-HTTP-Request connection timed out: #{Time.now - start_time} sec" if http.response_header.status.zero?
|
97
|
+
|
98
|
+
Response.new http.response_header.status,
|
99
|
+
convert_headers(http.response_header), http.response
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_request_url(url)
|
103
|
+
"%s://%s:%s%s" % [url.scheme, url.host, url.port, url.path]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Takes any header names with an underscore as a word separator and
|
107
|
+
# converts the name to camel case, where words are separated by a dash.
|
108
|
+
#
|
109
|
+
# E.g. CONTENT_TYPE becomes Content-Type.
|
110
|
+
def convert_headers(headers)
|
111
|
+
return headers unless headers.keys.any? { |k| k =~ /_/ }
|
112
|
+
|
113
|
+
result = {}
|
114
|
+
|
115
|
+
headers.each do |k, v|
|
116
|
+
words = k.split("_")
|
117
|
+
key = words.map { |w| w.downcase.capitalize }.join("-")
|
118
|
+
result[key] = v
|
119
|
+
end
|
120
|
+
|
121
|
+
result
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "httpi/adapter/base"
|
1
2
|
require "httpi/response"
|
2
3
|
|
3
4
|
module HTTPI
|
@@ -7,86 +8,55 @@ module HTTPI
|
|
7
8
|
#
|
8
9
|
# Adapter for the HTTPClient client.
|
9
10
|
# http://rubygems.org/gems/httpclient
|
10
|
-
class HTTPClient
|
11
|
+
class HTTPClient < Base
|
11
12
|
|
12
|
-
|
13
|
-
end
|
13
|
+
register :httpclient, :deps => %w(httpclient)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
@client
|
15
|
+
def initialize(request)
|
16
|
+
@request = request
|
17
|
+
@client = ::HTTPClient.new
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
# @see HTTPI.get
|
22
|
-
def get(request)
|
23
|
-
do_request request do |url, headers|
|
24
|
-
client.get url, nil, headers
|
25
|
-
end
|
26
|
-
end
|
20
|
+
attr_reader :client
|
27
21
|
|
28
|
-
# Executes
|
29
|
-
# @see HTTPI.
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
# Executes arbitrary HTTP requests.
|
23
|
+
# @see HTTPI.request
|
24
|
+
def request(method)
|
25
|
+
setup_client
|
26
|
+
respond_with @client.request(method, @request.url, nil, @request.body, @request.headers)
|
27
|
+
rescue OpenSSL::SSL::SSLError
|
28
|
+
raise SSLError
|
34
29
|
end
|
35
30
|
|
36
|
-
|
37
|
-
# @see HTTPI.head
|
38
|
-
def head(request)
|
39
|
-
do_request request do |url, headers|
|
40
|
-
client.head url, nil, headers
|
41
|
-
end
|
42
|
-
end
|
31
|
+
private
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
client.put url, body, headers
|
49
|
-
end
|
33
|
+
def setup_client
|
34
|
+
basic_setup
|
35
|
+
setup_auth if @request.auth.http?
|
36
|
+
setup_ssl_auth if @request.auth.ssl?
|
50
37
|
end
|
51
38
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
client.delete url, headers
|
57
|
-
end
|
39
|
+
def basic_setup
|
40
|
+
@client.proxy = @request.proxy if @request.proxy
|
41
|
+
@client.connect_timeout = @request.open_timeout if @request.open_timeout
|
42
|
+
@client.receive_timeout = @request.read_timeout if @request.read_timeout
|
58
43
|
end
|
59
44
|
|
60
|
-
|
61
|
-
|
62
|
-
def do_request(request)
|
63
|
-
setup_client request
|
64
|
-
respond_with yield(request.url, request.headers, request.body)
|
65
|
-
end
|
66
|
-
|
67
|
-
def setup_client(request)
|
68
|
-
basic_setup request
|
69
|
-
setup_auth request if request.auth.http?
|
70
|
-
setup_ssl_auth request.auth.ssl if request.auth.ssl?
|
45
|
+
def setup_auth
|
46
|
+
@client.set_auth @request.url, *@request.auth.credentials
|
71
47
|
end
|
72
48
|
|
73
|
-
def
|
74
|
-
|
75
|
-
client.connect_timeout = request.open_timeout if request.open_timeout
|
76
|
-
client.receive_timeout = request.read_timeout if request.read_timeout
|
77
|
-
end
|
49
|
+
def setup_ssl_auth
|
50
|
+
ssl = @request.auth.ssl
|
78
51
|
|
79
|
-
def setup_auth(request)
|
80
|
-
client.set_auth request.url, *request.auth.credentials
|
81
|
-
end
|
82
|
-
|
83
|
-
def setup_ssl_auth(ssl)
|
84
52
|
unless ssl.verify_mode == :none
|
85
|
-
client.ssl_config.client_cert = ssl.cert
|
86
|
-
client.ssl_config.client_key = ssl.cert_key
|
87
|
-
client.ssl_config.
|
53
|
+
@client.ssl_config.client_cert = ssl.cert
|
54
|
+
@client.ssl_config.client_key = ssl.cert_key
|
55
|
+
@client.ssl_config.add_trust_ca(ssl.ca_cert_file) if ssl.ca_cert_file
|
88
56
|
end
|
89
|
-
|
57
|
+
|
58
|
+
@client.ssl_config.verify_mode = ssl.openssl_verify_mode
|
59
|
+
@client.ssl_config.ssl_version = ssl.ssl_version if ssl.ssl_version
|
90
60
|
end
|
91
61
|
|
92
62
|
def respond_with(response)
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require "uri"
|
2
|
+
|
3
|
+
require "httpi/adapter/base"
|
2
4
|
require "httpi/response"
|
3
5
|
|
4
6
|
module HTTPI
|
@@ -8,89 +10,69 @@ module HTTPI
|
|
8
10
|
#
|
9
11
|
# Adapter for the Net::HTTP client.
|
10
12
|
# http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/
|
11
|
-
class NetHTTP
|
13
|
+
class NetHTTP < Base
|
14
|
+
|
15
|
+
register :net_http, :deps => %w(net/https)
|
12
16
|
|
13
17
|
def initialize(request)
|
14
|
-
|
18
|
+
@request = request
|
19
|
+
@client = create_client
|
15
20
|
end
|
16
21
|
|
17
22
|
attr_reader :client
|
18
23
|
|
19
|
-
# Executes
|
20
|
-
# @see HTTPI.
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Executes an HTTP POST request.
|
28
|
-
# @see HTTPI.post
|
29
|
-
def post(request)
|
30
|
-
do_request :post, request do |http, post|
|
31
|
-
post.body = request.body
|
32
|
-
http.request post
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Executes an HTTP HEAD request.
|
37
|
-
# @see HTTPI.head
|
38
|
-
def head(request)
|
39
|
-
do_request :head, request do |http, head|
|
40
|
-
http.request head
|
24
|
+
# Executes arbitrary HTTP requests.
|
25
|
+
# @see HTTPI.request
|
26
|
+
def request(method)
|
27
|
+
unless REQUEST_METHODS.include? method
|
28
|
+
raise NotSupportedError, "Net::HTTP does not support custom HTTP methods"
|
41
29
|
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Executes an HTTP PUT request.
|
45
|
-
# @see HTTPI.put
|
46
|
-
def put(request)
|
47
|
-
do_request :put, request do |http, put|
|
48
|
-
put.body = request.body
|
49
|
-
http.request put
|
50
|
-
end
|
51
|
-
end
|
52
30
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
do_request :delete, request do |http, delete|
|
57
|
-
http.request delete
|
31
|
+
do_request(method) do |http, http_request|
|
32
|
+
http_request.body = @request.body
|
33
|
+
http.request http_request
|
58
34
|
end
|
35
|
+
rescue OpenSSL::SSL::SSLError
|
36
|
+
raise SSLError
|
59
37
|
end
|
60
38
|
|
61
|
-
|
39
|
+
private
|
62
40
|
|
63
|
-
|
64
|
-
|
65
|
-
def new_client(request)
|
66
|
-
proxy_url = request.proxy || URI("")
|
41
|
+
def create_client
|
42
|
+
proxy_url = @request.proxy || URI("")
|
67
43
|
proxy = Net::HTTP::Proxy(proxy_url.host, proxy_url.port, proxy_url.user, proxy_url.password)
|
68
|
-
proxy.new
|
44
|
+
proxy.new(@request.url.host, @request.url.port)
|
69
45
|
end
|
70
46
|
|
71
|
-
def do_request(type
|
72
|
-
setup_client
|
73
|
-
setup_ssl_auth
|
47
|
+
def do_request(type)
|
48
|
+
setup_client
|
49
|
+
setup_ssl_auth if @request.auth.ssl?
|
74
50
|
|
75
|
-
respond_with(client.start do |http|
|
76
|
-
yield http, request_client(type
|
51
|
+
respond_with(@client.start do |http|
|
52
|
+
yield http, request_client(type)
|
77
53
|
end)
|
78
54
|
end
|
79
55
|
|
80
|
-
def setup_client
|
81
|
-
client.use_ssl = request.ssl?
|
82
|
-
client.open_timeout = request.open_timeout if request.open_timeout
|
83
|
-
client.read_timeout = request.read_timeout if request.read_timeout
|
56
|
+
def setup_client
|
57
|
+
@client.use_ssl = @request.ssl?
|
58
|
+
@client.open_timeout = @request.open_timeout if @request.open_timeout
|
59
|
+
@client.read_timeout = @request.read_timeout if @request.read_timeout
|
84
60
|
end
|
85
61
|
|
86
|
-
def setup_ssl_auth
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
62
|
+
def setup_ssl_auth
|
63
|
+
ssl = @request.auth.ssl
|
64
|
+
|
65
|
+
unless ssl.verify_mode == :none
|
66
|
+
@client.key = ssl.cert_key
|
67
|
+
@client.cert = ssl.cert
|
68
|
+
@client.ca_file = ssl.ca_cert_file if ssl.ca_cert_file
|
69
|
+
end
|
70
|
+
|
71
|
+
@client.verify_mode = ssl.openssl_verify_mode
|
72
|
+
@client.ssl_version = ssl.ssl_version if ssl.ssl_version
|
91
73
|
end
|
92
74
|
|
93
|
-
def request_client(type
|
75
|
+
def request_client(type)
|
94
76
|
request_class = case type
|
95
77
|
when :get then Net::HTTP::Get
|
96
78
|
when :post then Net::HTTP::Post
|
@@ -99,8 +81,8 @@ module HTTPI
|
|
99
81
|
when :delete then Net::HTTP::Delete
|
100
82
|
end
|
101
83
|
|
102
|
-
request_client = request_class.new request.url.request_uri, request.headers
|
103
|
-
request_client.basic_auth
|
84
|
+
request_client = request_class.new @request.url.request_uri, @request.headers
|
85
|
+
request_client.basic_auth *@request.auth.credentials if @request.auth.basic?
|
104
86
|
|
105
87
|
request_client
|
106
88
|
end
|
data/lib/httpi/auth/ssl.rb
CHANGED
@@ -10,6 +10,7 @@ module HTTPI
|
|
10
10
|
|
11
11
|
VERIFY_MODES = [:none, :peer, :fail_if_no_peer_cert, :client_once]
|
12
12
|
CERT_TYPES = [:pem, :der]
|
13
|
+
SSL_VERSIONS = [:TLSv1, :SSLv2, :SSLv3]
|
13
14
|
|
14
15
|
# Returns whether SSL configuration is present.
|
15
16
|
def present?
|
@@ -37,7 +38,11 @@ module HTTPI
|
|
37
38
|
|
38
39
|
# Sets the cert type to validate SSL certificates PEM|DER.
|
39
40
|
def cert_type=(type)
|
40
|
-
|
41
|
+
unless CERT_TYPES.include? type
|
42
|
+
raise ArgumentError, "Invalid SSL cert type #{type.inspect}\n" +
|
43
|
+
"Please specify one of #{CERT_TYPES.inspect}"
|
44
|
+
end
|
45
|
+
|
41
46
|
@cert_type = type
|
42
47
|
end
|
43
48
|
|
@@ -48,10 +53,29 @@ module HTTPI
|
|
48
53
|
|
49
54
|
# Sets the SSL verify mode. Expects one of <tt>HTTPI::Auth::SSL::VERIFY_MODES</tt>.
|
50
55
|
def verify_mode=(mode)
|
51
|
-
|
56
|
+
unless VERIFY_MODES.include? mode
|
57
|
+
raise ArgumentError, "Invalid SSL verify mode #{mode.inspect}\n" +
|
58
|
+
"Please specify one of #{VERIFY_MODES.inspect}"
|
59
|
+
end
|
60
|
+
|
52
61
|
@verify_mode = mode
|
53
62
|
end
|
54
63
|
|
64
|
+
# Returns the SSL version number. Defaults to <tt>nil</tt> (auto-negotiate).
|
65
|
+
def ssl_version
|
66
|
+
@ssl_version
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the SSL version number. Expects one of <tt>HTTPI::Auth::SSL::SSL_VERSIONS</tt>.
|
70
|
+
def ssl_version=(version)
|
71
|
+
unless SSL_VERSIONS.include? version
|
72
|
+
raise ArgumentError, "Invalid SSL version #{version.inspect}\n" +
|
73
|
+
"Please specify one of #{SSL_VERSIONS.inspect}"
|
74
|
+
end
|
75
|
+
|
76
|
+
@ssl_version = version
|
77
|
+
end
|
78
|
+
|
55
79
|
# Returns an <tt>OpenSSL::X509::Certificate</tt> for the +cert_file+.
|
56
80
|
def cert
|
57
81
|
@cert ||= (OpenSSL::X509::Certificate.new File.read(cert_file) if cert_file)
|